over-zero 0.0.33 → 0.0.35
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.
- package/dist/cjs/cli.cjs +38 -57
- package/dist/cjs/cli.js +38 -54
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/cli.native.js +42 -69
- package/dist/cjs/cli.native.js.map +1 -1
- package/dist/cjs/createUseQuery.cjs +72 -0
- package/dist/cjs/createUseQuery.js +46 -0
- package/dist/cjs/createUseQuery.js.map +6 -0
- package/dist/cjs/createUseQuery.native.js +88 -0
- package/dist/cjs/createUseQuery.native.js.map +1 -0
- package/dist/cjs/createZeroClient.cjs +10 -36
- package/dist/cjs/createZeroClient.js +8 -18
- package/dist/cjs/createZeroClient.js.map +2 -2
- package/dist/cjs/createZeroClient.native.js +9 -47
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/createZeroServer.cjs +5 -4
- package/dist/cjs/createZeroServer.js +8 -3
- package/dist/cjs/createZeroServer.js.map +1 -1
- package/dist/cjs/createZeroServer.native.js +39 -5
- package/dist/cjs/createZeroServer.native.js.map +1 -1
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +1 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/cli.js +38 -54
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/cli.mjs +38 -57
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/cli.native.js +42 -69
- package/dist/esm/cli.native.js.map +1 -1
- package/dist/esm/createUseQuery.js +34 -0
- package/dist/esm/createUseQuery.js.map +6 -0
- package/dist/esm/createUseQuery.mjs +49 -0
- package/dist/esm/createUseQuery.mjs.map +1 -0
- package/dist/esm/createUseQuery.native.js +62 -0
- package/dist/esm/createUseQuery.native.js.map +1 -0
- package/dist/esm/createZeroClient.js +10 -21
- package/dist/esm/createZeroClient.js.map +1 -1
- package/dist/esm/createZeroClient.mjs +11 -37
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +11 -49
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/createZeroServer.js +8 -3
- package/dist/esm/createZeroServer.js.map +1 -1
- package/dist/esm/createZeroServer.mjs +5 -4
- package/dist/esm/createZeroServer.mjs.map +1 -1
- package/dist/esm/createZeroServer.native.js +39 -5
- package/dist/esm/createZeroServer.native.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +1 -0
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +23 -0
- package/src/cli.ts +79 -102
- package/src/createUseQuery.tsx +141 -0
- package/src/createZeroClient.tsx +21 -119
- package/src/createZeroServer.ts +16 -7
- package/src/index.ts +1 -0
- package/types/createUseQuery.d.ts +22 -0
- package/types/createUseQuery.d.ts.map +1 -0
- package/types/createZeroClient.d.ts +6 -18
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/createZeroServer.d.ts +3 -3
- package/types/createZeroServer.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
package/src/cli.ts
CHANGED
|
@@ -274,23 +274,23 @@ const generate = defineCommand({
|
|
|
274
274
|
)
|
|
275
275
|
|
|
276
276
|
const allQueries = queryResults.flat()
|
|
277
|
-
const
|
|
278
|
-
const
|
|
277
|
+
const groupedQueriesOutput = generateGroupedQueriesFile(allQueries)
|
|
278
|
+
const syncedQueriesOutput = generateSyncedQueriesFile(allQueries)
|
|
279
279
|
|
|
280
|
-
const
|
|
281
|
-
resolve(generatedDir, '
|
|
282
|
-
|
|
280
|
+
const groupedChanged = writeFileIfChanged(
|
|
281
|
+
resolve(generatedDir, 'groupedQueries.ts'),
|
|
282
|
+
groupedQueriesOutput
|
|
283
283
|
)
|
|
284
|
-
const
|
|
285
|
-
resolve(generatedDir, '
|
|
286
|
-
|
|
284
|
+
const syncedChanged = writeFileIfChanged(
|
|
285
|
+
resolve(generatedDir, 'syncedQueries.ts'),
|
|
286
|
+
syncedQueriesOutput
|
|
287
287
|
)
|
|
288
288
|
|
|
289
|
-
if (
|
|
290
|
-
console.info(` 📝 Updated
|
|
289
|
+
if (groupedChanged) {
|
|
290
|
+
console.info(` 📝 Updated groupedQueries.ts`)
|
|
291
291
|
}
|
|
292
|
-
if (
|
|
293
|
-
console.info(` 📝 Updated
|
|
292
|
+
if (syncedChanged) {
|
|
293
|
+
console.info(` 📝 Updated syncedQueries.ts`)
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
console.info(
|
|
@@ -405,7 +405,7 @@ function generateTablesFile(modelFiles: string[]) {
|
|
|
405
405
|
return `// auto-generated by: over-zero generate\n// this is separate from models as otherwise you end up with circular types :/\n\n${exports}\n`
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
function
|
|
408
|
+
function generateGroupedQueriesFile(
|
|
409
409
|
queries: Array<{
|
|
410
410
|
name: string
|
|
411
411
|
params: string
|
|
@@ -416,30 +416,23 @@ function generateClientQueriesFile(
|
|
|
416
416
|
// get unique source files sorted
|
|
417
417
|
const sortedFiles = [...new Set(queries.map((q) => q.sourceFile))].sort()
|
|
418
418
|
|
|
419
|
-
// generate
|
|
420
|
-
const
|
|
421
|
-
.map((file) => `
|
|
419
|
+
// generate re-exports
|
|
420
|
+
const exports = sortedFiles
|
|
421
|
+
.map((file) => `export * as ${file} from '../queries/${file}'`)
|
|
422
422
|
.join('\n')
|
|
423
423
|
|
|
424
|
-
// generate namespaced export object
|
|
425
|
-
const namespaces = sortedFiles.map((file) => ` ${file}: ${file}Queries,`).join('\n')
|
|
426
|
-
|
|
427
424
|
return `/**
|
|
428
425
|
* auto-generated by: over-zero generate
|
|
429
426
|
*
|
|
430
|
-
*
|
|
427
|
+
* grouped query re-exports for minification-safe query identity.
|
|
431
428
|
* this file re-exports all query modules - while this breaks tree-shaking,
|
|
432
429
|
* queries are typically small and few in number even in larger apps.
|
|
433
430
|
*/
|
|
434
|
-
${
|
|
435
|
-
|
|
436
|
-
export const clientQueries = {
|
|
437
|
-
${namespaces}
|
|
438
|
-
}
|
|
431
|
+
${exports}
|
|
439
432
|
`
|
|
440
433
|
}
|
|
441
434
|
|
|
442
|
-
function
|
|
435
|
+
function generateSyncedQueriesFile(
|
|
443
436
|
queries: Array<{
|
|
444
437
|
name: string
|
|
445
438
|
params: string
|
|
@@ -459,95 +452,79 @@ function generateServerQueriesFile(
|
|
|
459
452
|
// sort file names for consistent output
|
|
460
453
|
const sortedFiles = Array.from(queryByFile.keys()).sort()
|
|
461
454
|
|
|
462
|
-
// generate imports
|
|
463
|
-
const queryImports = sortedFiles
|
|
464
|
-
.map((file) => `import * as ${file}Queries from '../queries/${file}'`)
|
|
465
|
-
.join('\n')
|
|
466
|
-
|
|
467
455
|
const imports = `// auto-generated by: over-zero generate
|
|
468
456
|
// server-side syncedQuery wrappers with validators
|
|
469
457
|
import { syncedQuery } from '@rocicorp/zero'
|
|
470
458
|
import * as v from 'valibot'
|
|
471
|
-
|
|
459
|
+
import * as Queries from './groupedQueries'
|
|
472
460
|
`
|
|
473
461
|
|
|
474
|
-
// generate
|
|
475
|
-
const
|
|
462
|
+
// generate grouped exports by namespace
|
|
463
|
+
const namespaceExports = sortedFiles
|
|
464
|
+
.map((file) => {
|
|
465
|
+
const fileQueries = queryByFile
|
|
466
|
+
.get(file)!
|
|
467
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
476
468
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
469
|
+
const queryDefs = fileQueries
|
|
470
|
+
.map((q) => {
|
|
471
|
+
// extract validator schema
|
|
472
|
+
const lines = q.valibotCode.split('\n').filter((l) => l.trim())
|
|
473
|
+
const schemaLineIndex = lines.findIndex((l) =>
|
|
474
|
+
l.startsWith('export const QueryParams')
|
|
475
|
+
)
|
|
484
476
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
477
|
+
let validatorDef = 'v.void()'
|
|
478
|
+
if (schemaLineIndex !== -1) {
|
|
479
|
+
const schemaLines: string[] = []
|
|
480
|
+
let openBraces = 0
|
|
481
|
+
let started = false
|
|
482
|
+
|
|
483
|
+
for (let i = schemaLineIndex; i < lines.length; i++) {
|
|
484
|
+
const line = lines[i]!
|
|
485
|
+
const cleaned = started
|
|
486
|
+
? line
|
|
487
|
+
: line.replace('export const QueryParams = ', '')
|
|
488
|
+
schemaLines.push(cleaned)
|
|
489
|
+
started = true
|
|
490
|
+
|
|
491
|
+
openBraces += (cleaned.match(/\{/g) || []).length
|
|
492
|
+
openBraces -= (cleaned.match(/\}/g) || []).length
|
|
493
|
+
openBraces += (cleaned.match(/\(/g) || []).length
|
|
494
|
+
openBraces -= (cleaned.match(/\)/g) || []).length
|
|
495
|
+
|
|
496
|
+
if (openBraces === 0 && schemaLines.length > 0) {
|
|
497
|
+
break
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
validatorDef = schemaLines.join('\n')
|
|
504
501
|
}
|
|
505
|
-
}
|
|
506
|
-
validatorDef = schemaLines.join('\n')
|
|
507
|
-
}
|
|
508
502
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// namespaced query name: file.queryName
|
|
516
|
-
const namespacedName = `${q.sourceFile}.${q.name}`
|
|
517
|
-
|
|
518
|
-
// for void queries, no arg parameter
|
|
519
|
-
const queryFn =
|
|
520
|
-
validatorDef === 'v.void()'
|
|
521
|
-
? `() => {
|
|
522
|
-
return ${q.sourceFile}Queries.${q.name}()
|
|
523
|
-
}`
|
|
524
|
-
: `(arg) => {
|
|
525
|
-
return ${q.sourceFile}Queries.${q.name}(arg)
|
|
526
|
-
}`
|
|
527
|
-
|
|
528
|
-
return `const ${q.name}Synced = syncedQuery('${namespacedName}', ${wrappedValidator}, ${queryFn})`
|
|
529
|
-
})
|
|
530
|
-
.join('\n\n')
|
|
503
|
+
// wrap validator in v.parser(v.tuple([...]))
|
|
504
|
+
const wrappedValidator =
|
|
505
|
+
validatorDef === 'v.void()'
|
|
506
|
+
? 'v.parser(v.tuple([]))'
|
|
507
|
+
: `v.parser(v.tuple([${validatorDef}]))`
|
|
531
508
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
509
|
+
// namespaced query name: file.queryName
|
|
510
|
+
const namespacedName = `${q.sourceFile}.${q.name}`
|
|
511
|
+
|
|
512
|
+
// for void queries, no arg parameter
|
|
513
|
+
const queryFn =
|
|
514
|
+
validatorDef === 'v.void()'
|
|
515
|
+
? `() => Queries.${file}.${q.name}()`
|
|
516
|
+
: `(arg) => Queries.${file}.${q.name}(arg)`
|
|
517
|
+
|
|
518
|
+
return ` ${q.name}: syncedQuery('${namespacedName}', ${wrappedValidator}, ${queryFn}),`
|
|
519
|
+
})
|
|
540
520
|
.join('\n')
|
|
541
|
-
|
|
521
|
+
|
|
522
|
+
return `export const ${file} = {\n${queryDefs}\n}`
|
|
542
523
|
})
|
|
543
|
-
.join('\n')
|
|
524
|
+
.join('\n\n')
|
|
544
525
|
|
|
545
526
|
return `${imports}
|
|
546
|
-
${
|
|
547
|
-
|
|
548
|
-
export const serverQueries = {
|
|
549
|
-
${namespaces}
|
|
550
|
-
}
|
|
527
|
+
${namespaceExports}
|
|
551
528
|
`
|
|
552
529
|
}
|
|
553
530
|
|
|
@@ -561,8 +538,8 @@ this folder is auto-generated by over-zero. do not edit files here directly.
|
|
|
561
538
|
- \`models.ts\` - exports all models from ../models
|
|
562
539
|
- \`types.ts\` - typescript types derived from table schemas
|
|
563
540
|
- \`tables.ts\` - exports table schemas for type inference
|
|
564
|
-
- \`
|
|
565
|
-
- \`
|
|
541
|
+
- \`groupedQueries.ts\` - namespaced query re-exports for client setup
|
|
542
|
+
- \`syncedQueries.ts\` - namespaced syncedQuery wrappers for server setup
|
|
566
543
|
|
|
567
544
|
## usage guidelines
|
|
568
545
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { syncedQuery } from '@rocicorp/zero'
|
|
2
|
+
import { useQuery as zeroUseQuery } from '@rocicorp/zero/react'
|
|
3
|
+
import { use, useMemo, type Context } from 'react'
|
|
4
|
+
|
|
5
|
+
import { useZeroDebug } from './helpers/useZeroDebug'
|
|
6
|
+
import { getQueryName } from './queryRegistry'
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
HumanReadable,
|
|
10
|
+
Query,
|
|
11
|
+
ReadonlyJSONValue,
|
|
12
|
+
SyncedQuery,
|
|
13
|
+
Schema as ZeroSchema,
|
|
14
|
+
} from '@rocicorp/zero'
|
|
15
|
+
|
|
16
|
+
export type UseQueryOptions = {
|
|
17
|
+
enabled?: boolean | undefined
|
|
18
|
+
ttl?: 'always' | 'never' | number | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type QueryResultDetails = ReturnType<typeof zeroUseQuery>[1]
|
|
22
|
+
export type QueryResult<TReturn> = readonly [HumanReadable<TReturn>, QueryResultDetails]
|
|
23
|
+
|
|
24
|
+
export type PlainQueryFn<
|
|
25
|
+
TArg = any,
|
|
26
|
+
TReturn extends Query<any, any, any> = Query<any, any, any>,
|
|
27
|
+
> = (args: TArg) => TReturn
|
|
28
|
+
|
|
29
|
+
// inline query type - only available when DisableInline is false
|
|
30
|
+
type InlineQuery<
|
|
31
|
+
Schema extends ZeroSchema,
|
|
32
|
+
TTable extends keyof Schema['tables'] & string,
|
|
33
|
+
TReturn,
|
|
34
|
+
> =
|
|
35
|
+
| Query<Schema, TTable, TReturn>
|
|
36
|
+
| SyncedQuery<any, any, any, any, Query<Schema, TTable, TReturn>>
|
|
37
|
+
|
|
38
|
+
// the useQuery hook type with conditional inline overload
|
|
39
|
+
export type UseQueryHook<
|
|
40
|
+
Schema extends ZeroSchema,
|
|
41
|
+
DisableInline extends boolean = false,
|
|
42
|
+
> = {
|
|
43
|
+
// overload 1: inline query (only when DisableInline is false)
|
|
44
|
+
<TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
45
|
+
query: DisableInline extends true ? never : InlineQuery<Schema, TTable, TReturn>,
|
|
46
|
+
options?: UseQueryOptions | boolean
|
|
47
|
+
): QueryResult<TReturn>;
|
|
48
|
+
|
|
49
|
+
// overload 2: plain function with params
|
|
50
|
+
<TArg, TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
51
|
+
fn: PlainQueryFn<TArg, Query<Schema, TTable, TReturn>>,
|
|
52
|
+
params: TArg,
|
|
53
|
+
options?: UseQueryOptions | boolean
|
|
54
|
+
): QueryResult<TReturn>;
|
|
55
|
+
|
|
56
|
+
// overload 3: plain function with no params
|
|
57
|
+
<TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
58
|
+
fn: PlainQueryFn<void, Query<Schema, TTable, TReturn>>,
|
|
59
|
+
options?: UseQueryOptions | boolean
|
|
60
|
+
): QueryResult<TReturn>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createUseQuery<
|
|
64
|
+
Schema extends ZeroSchema,
|
|
65
|
+
const DisableInline extends boolean = false,
|
|
66
|
+
>({
|
|
67
|
+
DisabledContext,
|
|
68
|
+
disableInlineQueries = false as DisableInline,
|
|
69
|
+
}: {
|
|
70
|
+
DisabledContext: Context<boolean>
|
|
71
|
+
disableInlineQueries?: DisableInline
|
|
72
|
+
}): UseQueryHook<Schema, DisableInline> {
|
|
73
|
+
const queryCache = new Map<string, SyncedQuery<any, any, any, any, any>>()
|
|
74
|
+
const parseAny = (x: unknown[]): [ReadonlyJSONValue] => [x[0] as ReadonlyJSONValue]
|
|
75
|
+
|
|
76
|
+
function useQuery(...args: any[]): any {
|
|
77
|
+
const disabled = use(DisabledContext)
|
|
78
|
+
const [queryOrFn, paramsOrOptions, optionsArg] = args
|
|
79
|
+
|
|
80
|
+
// detect which calling pattern is being used
|
|
81
|
+
const isPlainFunction = typeof queryOrFn === 'function' && !('queryName' in queryOrFn)
|
|
82
|
+
|
|
83
|
+
const { actualQuery, options } = useMemo(() => {
|
|
84
|
+
if (!isPlainFunction) {
|
|
85
|
+
// pattern 1: original api - useQuery(query, options)
|
|
86
|
+
return {
|
|
87
|
+
actualQuery: queryOrFn,
|
|
88
|
+
options: paramsOrOptions,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const fn = queryOrFn
|
|
93
|
+
const queryName = getQueryName(fn)
|
|
94
|
+
|
|
95
|
+
if (!queryName) {
|
|
96
|
+
throw new Error(`No query name?`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// determine if this is pattern 2 (with params) or pattern 3 (no params)
|
|
100
|
+
const hasParams =
|
|
101
|
+
optionsArg !== undefined ||
|
|
102
|
+
(paramsOrOptions &&
|
|
103
|
+
typeof paramsOrOptions === 'object' &&
|
|
104
|
+
!('enabled' in paramsOrOptions) &&
|
|
105
|
+
!('ttl' in paramsOrOptions))
|
|
106
|
+
|
|
107
|
+
const params = hasParams ? paramsOrOptions : undefined
|
|
108
|
+
const opts = hasParams ? optionsArg : paramsOrOptions
|
|
109
|
+
|
|
110
|
+
let synced = queryCache.get(queryName)
|
|
111
|
+
if (!synced) {
|
|
112
|
+
synced = syncedQuery(queryName, parseAny, (arg: ReadonlyJSONValue) => {
|
|
113
|
+
return fn(arg)
|
|
114
|
+
})
|
|
115
|
+
queryCache.set(queryName, synced)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// call the SyncedQuery with params if provided
|
|
119
|
+
const query = params !== undefined ? (synced as any)(params) : synced
|
|
120
|
+
|
|
121
|
+
return { actualQuery: query, options: opts }
|
|
122
|
+
}, [queryOrFn, paramsOrOptions, optionsArg, isPlainFunction])
|
|
123
|
+
|
|
124
|
+
console.info('running', { actualQuery, options, queryOrFn })
|
|
125
|
+
|
|
126
|
+
const out = zeroUseQuery(actualQuery, options)
|
|
127
|
+
|
|
128
|
+
if (process.env.NODE_ENV === 'development') {
|
|
129
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
130
|
+
useZeroDebug(actualQuery, options, out)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (disabled) {
|
|
134
|
+
return [null, { type: 'unknown' }] as never
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return out
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return useQuery as UseQueryHook<Schema, DisableInline>
|
|
141
|
+
}
|
package/src/createZeroClient.tsx
CHANGED
|
@@ -1,41 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useZero, ZeroProvider, useQuery as zeroUseQuery } from '@rocicorp/zero/react'
|
|
1
|
+
import { useZero, ZeroProvider } from '@rocicorp/zero/react'
|
|
3
2
|
import { createEmitter, mapObject } from '@vxrn/helpers'
|
|
4
3
|
import { createContext, use, useMemo, type ReactNode } from 'react'
|
|
5
4
|
|
|
6
5
|
import { createPermissions } from './createPermissions'
|
|
6
|
+
import { createUseQuery } from './createUseQuery'
|
|
7
7
|
import { createMutators } from './helpers/createMutators'
|
|
8
8
|
import { prettyFormatZeroQuery } from './helpers/prettyFormatZeroQuery'
|
|
9
|
-
import {
|
|
10
|
-
import { registerQuery, getQueryName } from './queryRegistry'
|
|
9
|
+
import { registerQuery } from './queryRegistry'
|
|
11
10
|
import { setAuthData, setSchema } from './state'
|
|
12
11
|
|
|
13
12
|
import type { AuthData, GenericModels, GetZeroMutators, ZeroEvent } from './types'
|
|
14
|
-
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ReadonlyJSONValue,
|
|
18
|
-
Row,
|
|
19
|
-
SyncedQuery,
|
|
20
|
-
Zero,
|
|
21
|
-
ZeroOptions,
|
|
22
|
-
Schema as ZeroSchema,
|
|
23
|
-
} from '@rocicorp/zero'
|
|
24
|
-
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
-
export type ClientQueries = Record<string, Record<string, (...args: any[]) => any>>
|
|
13
|
+
import type { Row, Zero, ZeroOptions, Schema as ZeroSchema } from '@rocicorp/zero'
|
|
14
|
+
|
|
15
|
+
export type GroupedQueries = Record<string, Record<string, (...args: any[]) => any>>
|
|
27
16
|
|
|
28
17
|
export function createZeroClient<
|
|
29
18
|
Schema extends ZeroSchema,
|
|
30
19
|
Models extends GenericModels,
|
|
20
|
+
const DisableInlineQueries extends boolean = false,
|
|
31
21
|
>({
|
|
32
22
|
schema,
|
|
33
23
|
models,
|
|
34
|
-
|
|
24
|
+
groupedQueries,
|
|
25
|
+
disableInlineQueries = false as DisableInlineQueries,
|
|
35
26
|
}: {
|
|
36
27
|
schema: Schema
|
|
37
28
|
models: Models
|
|
38
|
-
|
|
29
|
+
groupedQueries: GroupedQueries
|
|
30
|
+
disableInlineQueries?: DisableInlineQueries
|
|
39
31
|
}) {
|
|
40
32
|
type ZeroMutators = GetZeroMutators<Models>
|
|
41
33
|
type ZeroInstance = Zero<Schema, ZeroMutators>
|
|
@@ -43,8 +35,8 @@ export function createZeroClient<
|
|
|
43
35
|
|
|
44
36
|
setSchema(schema)
|
|
45
37
|
|
|
46
|
-
// build query registry from
|
|
47
|
-
for (const [namespace, queries] of Object.entries(
|
|
38
|
+
// build query registry from grouped queries
|
|
39
|
+
for (const [namespace, queries] of Object.entries(groupedQueries)) {
|
|
48
40
|
for (const [name, fn] of Object.entries(queries)) {
|
|
49
41
|
registerQuery(fn, `${namespace}.${name}`)
|
|
50
42
|
}
|
|
@@ -80,6 +72,11 @@ export function createZeroClient<
|
|
|
80
72
|
const AuthDataContext = createContext<AuthData>({} as AuthData)
|
|
81
73
|
const useAuthData = () => use(AuthDataContext)
|
|
82
74
|
|
|
75
|
+
const useQuery = createUseQuery<Schema, DisableInlineQueries>({
|
|
76
|
+
DisabledContext,
|
|
77
|
+
disableInlineQueries,
|
|
78
|
+
})
|
|
79
|
+
|
|
83
80
|
// we don't want flickers as you move around and these queries are re-run
|
|
84
81
|
// and things generally aren't changing with permissions rapidly, so lets
|
|
85
82
|
// cache the last results and use that when first rendering, they will
|
|
@@ -112,7 +109,9 @@ export function createZeroClient<
|
|
|
112
109
|
})
|
|
113
110
|
})()
|
|
114
111
|
|
|
115
|
-
|
|
112
|
+
// note: usePermission is internal and uses inline queries intentionally
|
|
113
|
+
// if disableInlineQueries is enabled, this will error and need a cast
|
|
114
|
+
const [data, status] = useQuery(query as any, {
|
|
116
115
|
enabled: Boolean(enabled && permission && authData && objOrId),
|
|
117
116
|
})
|
|
118
117
|
|
|
@@ -135,103 +134,6 @@ export function createZeroClient<
|
|
|
135
134
|
return allowed
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
type PlainQueryFn<
|
|
139
|
-
TArg = any,
|
|
140
|
-
TReturn extends Query<any, any, any> = Query<any, any, any>,
|
|
141
|
-
> = (args: TArg) => TReturn
|
|
142
|
-
|
|
143
|
-
type UseQueryOptions = {
|
|
144
|
-
enabled?: boolean | undefined
|
|
145
|
-
ttl?: 'always' | 'never' | number | undefined
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
type QueryResultDetails = ReturnType<typeof zeroUseQuery>[1]
|
|
149
|
-
type QueryResult<TReturn> = readonly [HumanReadable<TReturn>, QueryResultDetails]
|
|
150
|
-
|
|
151
|
-
const queryCache = new Map<string, SyncedQuery<any, any, any, any, any>>()
|
|
152
|
-
|
|
153
|
-
const parseAny = (x: unknown[]): [ReadonlyJSONValue] => [x[0] as ReadonlyJSONValue]
|
|
154
|
-
|
|
155
|
-
// Overload 1: Original API - Query or SyncedQuery with options
|
|
156
|
-
function useQuery<TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
157
|
-
query:
|
|
158
|
-
| Query<Schema, TTable, TReturn>
|
|
159
|
-
| SyncedQuery<any, any, any, any, Query<Schema, TTable, TReturn>>,
|
|
160
|
-
options?: UseQueryOptions | boolean
|
|
161
|
-
): QueryResult<TReturn>
|
|
162
|
-
|
|
163
|
-
// Overload 2: Plain function with params
|
|
164
|
-
function useQuery<TArg, TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
165
|
-
fn: PlainQueryFn<TArg, Query<Schema, TTable, TReturn>>,
|
|
166
|
-
params: TArg,
|
|
167
|
-
options?: UseQueryOptions | boolean
|
|
168
|
-
): QueryResult<TReturn>
|
|
169
|
-
|
|
170
|
-
// Overload 3: Plain function with no params
|
|
171
|
-
function useQuery<TTable extends keyof Schema['tables'] & string, TReturn>(
|
|
172
|
-
fn: PlainQueryFn<void, Query<Schema, TTable, TReturn>>,
|
|
173
|
-
options?: UseQueryOptions | boolean
|
|
174
|
-
): QueryResult<TReturn>
|
|
175
|
-
|
|
176
|
-
// Implementation - keep it simple with any
|
|
177
|
-
function useQuery(...args: any[]): any {
|
|
178
|
-
const disabled = use(DisabledContext)
|
|
179
|
-
const [queryOrFn, paramsOrOptions, optionsArg] = args
|
|
180
|
-
|
|
181
|
-
// Detect which calling pattern is being used
|
|
182
|
-
const isPlainFunction = typeof queryOrFn === 'function' && !('queryName' in queryOrFn)
|
|
183
|
-
|
|
184
|
-
const { actualQuery, options } = useMemo(() => {
|
|
185
|
-
if (!isPlainFunction) {
|
|
186
|
-
// Pattern 1: Original API - useQuery(query, options)
|
|
187
|
-
return {
|
|
188
|
-
actualQuery: queryOrFn,
|
|
189
|
-
options: paramsOrOptions,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const fn = queryOrFn
|
|
194
|
-
const queryName = getQueryName(fn) || fn.name || 'anonymousQuery'
|
|
195
|
-
|
|
196
|
-
// Determine if this is Pattern 2 (with params) or Pattern 3 (no params)
|
|
197
|
-
const hasParams =
|
|
198
|
-
optionsArg !== undefined ||
|
|
199
|
-
(paramsOrOptions &&
|
|
200
|
-
typeof paramsOrOptions === 'object' &&
|
|
201
|
-
!('enabled' in paramsOrOptions) &&
|
|
202
|
-
!('ttl' in paramsOrOptions))
|
|
203
|
-
|
|
204
|
-
const params = hasParams ? paramsOrOptions : undefined
|
|
205
|
-
const opts = hasParams ? optionsArg : paramsOrOptions
|
|
206
|
-
|
|
207
|
-
let synced = queryCache.get(queryName)
|
|
208
|
-
if (!synced) {
|
|
209
|
-
synced = syncedQuery(queryName, parseAny, (arg: ReadonlyJSONValue) => {
|
|
210
|
-
return fn(arg)
|
|
211
|
-
})
|
|
212
|
-
queryCache.set(queryName, synced)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Call the SyncedQuery with params if provided
|
|
216
|
-
const query = params !== undefined ? (synced as any)(params) : synced
|
|
217
|
-
|
|
218
|
-
return { actualQuery: query, options: opts }
|
|
219
|
-
}, [queryOrFn, paramsOrOptions, optionsArg, isPlainFunction])
|
|
220
|
-
|
|
221
|
-
const out = zeroUseQuery(actualQuery, options)
|
|
222
|
-
|
|
223
|
-
if (process.env.NODE_ENV === 'development') {
|
|
224
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
225
|
-
useZeroDebug(actualQuery, options, out)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (disabled) {
|
|
229
|
-
return [null, { type: 'unknown' }] as never
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return out
|
|
233
|
-
}
|
|
234
|
-
|
|
235
137
|
const ProvideZero = ({
|
|
236
138
|
children,
|
|
237
139
|
authData: authDataIn,
|
package/src/createZeroServer.ts
CHANGED
|
@@ -24,8 +24,8 @@ import type {
|
|
|
24
24
|
} from '@rocicorp/zero'
|
|
25
25
|
import type { TransactionProviderInput } from '@rocicorp/zero/pg'
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
export type
|
|
27
|
+
// grouped synced queries: { namespace: { queryName: SyncedQuery } }
|
|
28
|
+
export type SyncedQueries = Record<
|
|
29
29
|
string,
|
|
30
30
|
Record<string, SyncedQuery<any, any, any, any, any>>
|
|
31
31
|
>
|
|
@@ -34,12 +34,13 @@ export function createZeroServer<
|
|
|
34
34
|
Schema extends ZeroSchema,
|
|
35
35
|
Models extends GenericModels,
|
|
36
36
|
ServerActions extends Record<string, unknown>,
|
|
37
|
+
Queries extends SyncedQueries = Record<string, never>,
|
|
37
38
|
>({
|
|
38
39
|
createServerActions,
|
|
39
40
|
database,
|
|
40
41
|
schema,
|
|
41
42
|
models,
|
|
42
|
-
|
|
43
|
+
syncedQueries,
|
|
43
44
|
}: {
|
|
44
45
|
/**
|
|
45
46
|
* The DB connection string, same as ZERO_UPSTREAM_DB
|
|
@@ -48,7 +49,7 @@ export function createZeroServer<
|
|
|
48
49
|
schema: Schema
|
|
49
50
|
models: Models
|
|
50
51
|
createServerActions: () => ServerActions
|
|
51
|
-
|
|
52
|
+
syncedQueries?: Queries
|
|
52
53
|
}) {
|
|
53
54
|
setSchema(schema)
|
|
54
55
|
|
|
@@ -111,6 +112,16 @@ export function createZeroServer<
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
// build flat lookup map from grouped syncedQueries: "namespace.queryName" => SyncedQuery
|
|
116
|
+
const queryLookup = new Map<string, SyncedQuery<any, any, any, any, any>>()
|
|
117
|
+
if (syncedQueries) {
|
|
118
|
+
for (const [namespace, queries] of Object.entries(syncedQueries)) {
|
|
119
|
+
for (const [queryName, syncedQuery] of Object.entries(queries)) {
|
|
120
|
+
queryLookup.set(`${namespace}.${queryName}`, syncedQuery)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
114
125
|
const handleQueryRequest = async ({
|
|
115
126
|
authData,
|
|
116
127
|
request,
|
|
@@ -119,9 +130,7 @@ export function createZeroServer<
|
|
|
119
130
|
request: Request
|
|
120
131
|
}) => {
|
|
121
132
|
function getQuery(name: string, args: readonly ReadonlyJSONValue[]) {
|
|
122
|
-
|
|
123
|
-
const [namespace, queryName] = name.split('.')
|
|
124
|
-
const q = serverQueries?.[namespace!]?.[queryName!]
|
|
133
|
+
const q = queryLookup.get(name)
|
|
125
134
|
if (!q) {
|
|
126
135
|
throw new Error(`No such query: ${name}`)
|
|
127
136
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useQuery as zeroUseQuery } from '@rocicorp/zero/react';
|
|
2
|
+
import { type Context } from 'react';
|
|
3
|
+
import type { HumanReadable, Query, SyncedQuery, Schema as ZeroSchema } from '@rocicorp/zero';
|
|
4
|
+
export type UseQueryOptions = {
|
|
5
|
+
enabled?: boolean | undefined;
|
|
6
|
+
ttl?: 'always' | 'never' | number | undefined;
|
|
7
|
+
};
|
|
8
|
+
type QueryResultDetails = ReturnType<typeof zeroUseQuery>[1];
|
|
9
|
+
export type QueryResult<TReturn> = readonly [HumanReadable<TReturn>, QueryResultDetails];
|
|
10
|
+
export type PlainQueryFn<TArg = any, TReturn extends Query<any, any, any> = Query<any, any, any>> = (args: TArg) => TReturn;
|
|
11
|
+
type InlineQuery<Schema extends ZeroSchema, TTable extends keyof Schema['tables'] & string, TReturn> = Query<Schema, TTable, TReturn> | SyncedQuery<any, any, any, any, Query<Schema, TTable, TReturn>>;
|
|
12
|
+
export type UseQueryHook<Schema extends ZeroSchema, DisableInline extends boolean = false> = {
|
|
13
|
+
<TTable extends keyof Schema['tables'] & string, TReturn>(query: DisableInline extends true ? never : InlineQuery<Schema, TTable, TReturn>, options?: UseQueryOptions | boolean): QueryResult<TReturn>;
|
|
14
|
+
<TArg, TTable extends keyof Schema['tables'] & string, TReturn>(fn: PlainQueryFn<TArg, Query<Schema, TTable, TReturn>>, params: TArg, options?: UseQueryOptions | boolean): QueryResult<TReturn>;
|
|
15
|
+
<TTable extends keyof Schema['tables'] & string, TReturn>(fn: PlainQueryFn<void, Query<Schema, TTable, TReturn>>, options?: UseQueryOptions | boolean): QueryResult<TReturn>;
|
|
16
|
+
};
|
|
17
|
+
export declare function createUseQuery<Schema extends ZeroSchema, const DisableInline extends boolean = false>({ DisabledContext, disableInlineQueries, }: {
|
|
18
|
+
DisabledContext: Context<boolean>;
|
|
19
|
+
disableInlineQueries?: DisableInline;
|
|
20
|
+
}): UseQueryHook<Schema, DisableInline>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=createUseQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createUseQuery.d.ts","sourceRoot":"","sources":["../src/createUseQuery.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,OAAO,CAAA;AAKlD,OAAO,KAAK,EACV,aAAa,EACb,KAAK,EAEL,WAAW,EACX,MAAM,IAAI,UAAU,EACrB,MAAM,gBAAgB,CAAA;AAEvB,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;CAC9C,CAAA;AAED,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAC5D,MAAM,MAAM,WAAW,CAAC,OAAO,IAAI,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAA;AAExF,MAAM,MAAM,YAAY,CACtB,IAAI,GAAG,GAAG,EACV,OAAO,SAAS,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IACzD,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;AAG3B,KAAK,WAAW,CACd,MAAM,SAAS,UAAU,EACzB,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAC9C,OAAO,IAEL,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,GAC9B,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAGnE,MAAM,MAAM,YAAY,CACtB,MAAM,SAAS,UAAU,EACzB,aAAa,SAAS,OAAO,GAAG,KAAK,IACnC;IAEF,CAAC,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EACtD,KAAK,EAAE,aAAa,SAAS,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAChF,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,GAClC,WAAW,CAAC,OAAO,CAAC,CAAC;IAGxB,CAAC,IAAI,EAAE,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EAC5D,EAAE,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,EACtD,MAAM,EAAE,IAAI,EACZ,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,GAClC,WAAW,CAAC,OAAO,CAAC,CAAC;IAGxB,CAAC,MAAM,SAAS,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,OAAO,EACtD,EAAE,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,EACtD,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,GAClC,WAAW,CAAC,OAAO,CAAC,CAAA;CACxB,CAAA;AAED,wBAAgB,cAAc,CAC5B,MAAM,SAAS,UAAU,EACzB,KAAK,CAAC,aAAa,SAAS,OAAO,GAAG,KAAK,EAC3C,EACA,eAAe,EACf,oBAA6C,GAC9C,EAAE;IACD,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IACjC,oBAAoB,CAAC,EAAE,aAAa,CAAA;CACrC,GAAG,YAAY,CAAC,MAAM,EAAE,aAAa,CAAC,CAqEtC"}
|