@mongrov/data-access 0.1.0-alpha.0
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/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +51 -0
- package/dist/context.js.map +1 -0
- package/dist/define.d.ts +26 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +60 -0
- package/dist/define.js.map +1 -0
- package/dist/dispatcher.d.ts +62 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +91 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eslint/index.d.ts +52 -0
- package/dist/eslint/index.d.ts.map +1 -0
- package/dist/eslint/index.js +45 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/rules/no-storage-engine-imports.d.ts +22 -0
- package/dist/eslint/rules/no-storage-engine-imports.d.ts.map +1 -0
- package/dist/eslint/rules/no-storage-engine-imports.js +145 -0
- package/dist/eslint/rules/no-storage-engine-imports.js.map +1 -0
- package/dist/hooks.d.ts +67 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +234 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/invalidation.d.ts +29 -0
- package/dist/invalidation.d.ts.map +1 -0
- package/dist/invalidation.js +106 -0
- package/dist/invalidation.js.map +1 -0
- package/dist/tenant.d.ts +18 -0
- package/dist/tenant.d.ts.map +1 -0
- package/dist/tenant.js +31 -0
- package/dist/tenant.js.map +1 -0
- package/dist/types.d.ts +127 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Mongrov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# @mongrov/data-access
|
|
2
|
+
|
|
3
|
+
Named-query / mutation / event registry with engine dispatch for `@mongrov` apps.
|
|
4
|
+
|
|
5
|
+
Screens never import storage engines. Instead, they read via `useAppQuery(name, input)` and write via `useAppMutation(name)` against a single **registry** the app assembles at startup. The provider dispatches to the right engine (DuckDB, RxDB, or KV) and Zod-parses the result before it reaches the UI.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Define API** — `defineQuery`, `defineMutation`, `defineEvent` produce typed handles with a `__kind` discriminator and engine-specific config (SQL for DuckDB, observable factory for RxDB, key builder for KV).
|
|
10
|
+
- **Engine dispatch** — `duckdb` (analytics), `rxdb` (reactive stream), `kv` (single-key read). Apps wire concrete adapters into `DataAccessProvider`.
|
|
11
|
+
- **Authorize gate** — optional `authorize(input, ctx)` runs before every query and mutation; `false` throws `AuthorizationError`.
|
|
12
|
+
- **Tenant auto-binding** — `{ brand, familyId }` from the active `RequestContext` are merged into DuckDB params automatically. Screens never pass tenant.
|
|
13
|
+
- **Invalidation event bus** — `mitt`-backed exact-match dispatch plus a glob wrapper (`hrv:*`, `sleep:**`); mutations declare `invalidates`, queries declare `invalidatedBy`, refetches happen automatically.
|
|
14
|
+
- **Optimistic mutations** — `optimistic(input, ctx)` surfaces immediately as `data`; reverts on failure.
|
|
15
|
+
- **Cache defaults** — 30s `staleTime`, 5min `gcTime`, overridable per query.
|
|
16
|
+
- **ESLint plugin** — `no-storage-engine-imports` bans direct `@mongrov/db`, `@mongrov/analytics`, `@mongrov/collab` imports outside the registry directory.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @mongrov/data-access
|
|
22
|
+
# Peer deps
|
|
23
|
+
pnpm add @tanstack/react-query zod
|
|
24
|
+
# Optional peers (only what you use)
|
|
25
|
+
pnpm add @mongrov/db @mongrov/analytics react react-native eslint
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Registry pattern
|
|
29
|
+
|
|
30
|
+
Assemble a registry once — typically under `src/data/` in your app.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// src/data/queries.ts
|
|
34
|
+
import { defineQuery } from '@mongrov/data-access';
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
|
|
37
|
+
export const hrvLast7Days = defineQuery({
|
|
38
|
+
engine: 'duckdb',
|
|
39
|
+
input: z.object({ userId: z.string() }),
|
|
40
|
+
output: z.array(z.object({ day: z.string(), rmssd: z.number() })),
|
|
41
|
+
sql: 'SELECT day, rmssd FROM hrv WHERE user_id = $userId AND brand = $brand AND family_id = $familyId ORDER BY day DESC LIMIT 7',
|
|
42
|
+
invalidatedBy: ['hrv:*'],
|
|
43
|
+
staleTime: 60_000,
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
// src/data/mutations.ts
|
|
49
|
+
import { defineMutation } from '@mongrov/data-access';
|
|
50
|
+
import { z } from 'zod';
|
|
51
|
+
|
|
52
|
+
export const recordHrvSample = defineMutation({
|
|
53
|
+
input: z.object({ rmssd: z.number(), ts: z.string() }),
|
|
54
|
+
output: z.object({ id: z.string() }),
|
|
55
|
+
exec: async (input, ctx) => {
|
|
56
|
+
// Direct engine access is legal here because this file lives in
|
|
57
|
+
// src/data/, which the ESLint rule's `allowedDirs` grants an
|
|
58
|
+
// exception to.
|
|
59
|
+
return await analytics.insert('hrv', { ...input, userId: ctx.requesterUserId });
|
|
60
|
+
},
|
|
61
|
+
invalidates: ['hrv:sample:added'],
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// src/data/registry.ts
|
|
67
|
+
import { hrvLast7Days } from './queries';
|
|
68
|
+
import { recordHrvSample } from './mutations';
|
|
69
|
+
|
|
70
|
+
export const registry = {
|
|
71
|
+
queries: { 'hrv.last7Days': hrvLast7Days },
|
|
72
|
+
mutations: { 'hrv.record': recordHrvSample },
|
|
73
|
+
events: {},
|
|
74
|
+
} as const;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Mount the provider at the app root:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// src/app/_layout.tsx
|
|
81
|
+
import { DataAccessProvider } from '@mongrov/data-access';
|
|
82
|
+
import { registry } from '@/data/registry';
|
|
83
|
+
import { analyticsEngine } from '@/data/engines';
|
|
84
|
+
|
|
85
|
+
export default function RootLayout() {
|
|
86
|
+
return (
|
|
87
|
+
<DataAccessProvider
|
|
88
|
+
registry={registry}
|
|
89
|
+
engines={{ duckdb: analyticsEngine }}
|
|
90
|
+
context={() => ({
|
|
91
|
+
requesterUserId: session.userId,
|
|
92
|
+
brand: 'zivaone',
|
|
93
|
+
familyId: session.familyId,
|
|
94
|
+
now: () => new Date(),
|
|
95
|
+
})}
|
|
96
|
+
>
|
|
97
|
+
<Slot />
|
|
98
|
+
</DataAccessProvider>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Screens consume via hooks — no engine imports:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// src/features/hrv/screen.tsx
|
|
107
|
+
import { useAppQuery, useAppMutation } from '@mongrov/data-access';
|
|
108
|
+
|
|
109
|
+
export function HrvScreen() {
|
|
110
|
+
const { data, loading, error } = useAppQuery<
|
|
111
|
+
{ userId: string },
|
|
112
|
+
Array<{ day: string; rmssd: number }>
|
|
113
|
+
>('hrv.last7Days', { userId: 'me' });
|
|
114
|
+
|
|
115
|
+
const record = useAppMutation<{ rmssd: number; ts: string }, { id: string }>('hrv.record');
|
|
116
|
+
|
|
117
|
+
if (loading) return <ActivityIndicator />;
|
|
118
|
+
if (error) return <Text>{error.message}</Text>;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<>
|
|
122
|
+
<List data={data} />
|
|
123
|
+
<Button
|
|
124
|
+
title="Record sample"
|
|
125
|
+
onPress={() => record.mutate({ rmssd: 42, ts: new Date().toISOString() })}
|
|
126
|
+
/>
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
### `defineQuery(config)`
|
|
135
|
+
|
|
136
|
+
Returns a `QueryDefinition<Input, Output>` handle with `__kind: 'query'`. Config union by `engine`:
|
|
137
|
+
|
|
138
|
+
| Field | duckdb | rxdb | kv |
|
|
139
|
+
| --------------- | ------------- | ----------------------------------- | --------------------------- |
|
|
140
|
+
| Required | `sql: string` | `query: (db, input) => Observable` | `keyBuilder: (input) => string` |
|
|
141
|
+
| Common | `output: ZodType` (required), `input?: ZodType`, `invalidatedBy?: string[]`, `authorize?`, `staleTime?`, `gcTime?` |||
|
|
142
|
+
|
|
143
|
+
- **duckdb** — params include Zod-parsed input **plus** `brand` and `familyId` from `RequestContext`, injected as `$brand` / `$familyId` for `analytics.execute(sql, params)`.
|
|
144
|
+
- **rxdb** — returned observable is subscribed in `useAppQuery`. Each emission is Zod-parsed. Unsubscribes on unmount.
|
|
145
|
+
- **kv** — `keyBuilder(input)` produces the storage key; the engine's `get(key)` returns the value. No automatic invalidation.
|
|
146
|
+
|
|
147
|
+
### `defineMutation(config)`
|
|
148
|
+
|
|
149
|
+
Returns a `MutationDefinition<Input, Output>` handle with `__kind: 'mutation'`.
|
|
150
|
+
|
|
151
|
+
- `exec(input, ctx) => Promise<Output>` — required.
|
|
152
|
+
- `invalidates?: string[]` — event bus patterns fired on success.
|
|
153
|
+
- `authorize?(input, ctx) => boolean | Promise<boolean>` — false throws `AuthorizationError`.
|
|
154
|
+
- `optimistic?(input, ctx) => Output` — surfaces as `data` while pending; reverts on failure.
|
|
155
|
+
- `input?` / `output?` — Zod schemas; validation is applied on both sides.
|
|
156
|
+
|
|
157
|
+
### `defineEvent<TPayload>()`
|
|
158
|
+
|
|
159
|
+
Returns an `EventDefinition<TPayload>` handle with `__kind: 'event'`. Payload type flows through `useAppEvent`.
|
|
160
|
+
|
|
161
|
+
### `useAppQuery<Input, Output>(name, input?)`
|
|
162
|
+
|
|
163
|
+
Returns `{ data, loading, error, refetch, isStale }`.
|
|
164
|
+
|
|
165
|
+
- For **duckdb** / **kv**, backed by TanStack `useQuery`. Subscribes to every `invalidatedBy` pattern on mount and invalidates its own cache when a match fires.
|
|
166
|
+
- For **rxdb**, subscribes to the observable via `useEffect`. `refetch()` is a no-op — the stream drives updates. Zod-parses on every emission; parse failure surfaces as `error`.
|
|
167
|
+
- Cache defaults: `staleTime = 30_000`, `gcTime = 300_000`, both overridable in the query definition.
|
|
168
|
+
|
|
169
|
+
### `useAppMutation<Input, Output>(name)`
|
|
170
|
+
|
|
171
|
+
Returns `{ mutate, mutateAsync, loading, error, data, reset }`. On success, emits every entry in `invalidates` on the bus. On failure, clears optimistic state so subsequent renders see `data === undefined`.
|
|
172
|
+
|
|
173
|
+
### `useAppEvent<TPayload>(name, handler)`
|
|
174
|
+
|
|
175
|
+
Subscribes to exact-match `name`. Handler ref stays fresh across renders. Unsubscribes on unmount. For glob semantics, call `bus.subscribePattern` directly via `useDataAccessRuntime`.
|
|
176
|
+
|
|
177
|
+
### `useRequestContext(): RequestContext`
|
|
178
|
+
|
|
179
|
+
Reads the provider's `context()` callback on every render, so session updates propagate without remount.
|
|
180
|
+
|
|
181
|
+
### `DataAccessProvider`
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
interface DataAccessProviderConfig {
|
|
185
|
+
registry: Registry;
|
|
186
|
+
engines: { duckdb?: DuckdbEngine; rxdb?: RxdbEngine; kv?: KvEngine };
|
|
187
|
+
context: () => RequestContext;
|
|
188
|
+
bus?: EventBus; // optional; one is minted per provider
|
|
189
|
+
queryClient?: QueryClient; // optional; one is minted per provider
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Nesting providers is not supported in v0.1 — mount exactly one at the app root.
|
|
194
|
+
|
|
195
|
+
### `createEventBus(): EventBus`
|
|
196
|
+
|
|
197
|
+
Standalone factory for tests or non-React callers. Exposes `emit`, `subscribe`, `subscribePattern`. Pattern semantics:
|
|
198
|
+
|
|
199
|
+
- `hrv:*` — matches one `:`-delimited segment.
|
|
200
|
+
- `hrv:**` — matches one or more segments.
|
|
201
|
+
- Empty pattern rejected. Case-sensitive.
|
|
202
|
+
|
|
203
|
+
### Errors
|
|
204
|
+
|
|
205
|
+
- `DataAccessError` — base class with `.code`. Codes: `engine_missing`, `zod_parse_failed`, `authorization_denied`, `not_implemented`.
|
|
206
|
+
- `AuthorizationError` extends `DataAccessError` (`code: 'authorization_denied'`).
|
|
207
|
+
- `NotImplementedError` extends `DataAccessError` (`code: 'not_implemented'`); reserved for stubs.
|
|
208
|
+
|
|
209
|
+
## ESLint plugin
|
|
210
|
+
|
|
211
|
+
Blocks direct storage-engine imports outside the registry directory.
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
// eslint.config.mjs
|
|
215
|
+
import dataAccessPlugin from '@mongrov/data-access/eslint';
|
|
216
|
+
|
|
217
|
+
export default [
|
|
218
|
+
{
|
|
219
|
+
plugins: { '@mongrov/data-access': dataAccessPlugin },
|
|
220
|
+
rules: {
|
|
221
|
+
'@mongrov/data-access/no-storage-engine-imports': ['error', {
|
|
222
|
+
allowedDirs: ['src/data'],
|
|
223
|
+
}],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- Default block list: `@mongrov/analytics`, `@mongrov/collab`, `@mongrov/db` (subpaths included — `@mongrov/db/kv` is blocked by the same rule).
|
|
230
|
+
- `allowedDirs` — path fragments that suppress the rule; the registry directory typically goes here.
|
|
231
|
+
- `blockedPackages` — override the default block list.
|
|
232
|
+
- Covers `import`, dynamic `import()`, and CommonJS `require()`.
|
|
233
|
+
|
|
234
|
+
## Testing patterns
|
|
235
|
+
|
|
236
|
+
The package ships with a full vitest suite you can mirror in-app.
|
|
237
|
+
|
|
238
|
+
- **In-memory event bus** — `createEventBus()` is standalone and needs no engine wiring.
|
|
239
|
+
- **Fake DuckDB engine** — implement `DuckdbEngine.execute(sql, params)` in a couple of lines to drive query tests.
|
|
240
|
+
- **Fake RxDB observable** — a `{ subscribe(observer) }` object with `next` / `error` is enough for `useAppQuery('name.rxdb', ...)`.
|
|
241
|
+
- **QueryClient with `retry: false`** — avoid retry timeouts in error-surface tests:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const client = new QueryClient({
|
|
245
|
+
defaultOptions: {
|
|
246
|
+
queries: { retry: false, gcTime: 0 },
|
|
247
|
+
mutations: { retry: false },
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
See `src/__tests__/integration.test.tsx` for a full smoke test wiring registry + provider + hooks together.
|
|
253
|
+
|
|
254
|
+
## Version
|
|
255
|
+
|
|
256
|
+
`0.1.0-alpha.0` — first cut. Follow-ups tracked in [`.specifica/features/data-access/tasks.md`](../../../.specifica/features/data-access/tasks.md):
|
|
257
|
+
|
|
258
|
+
- Row-level invalidation
|
|
259
|
+
- Streaming query results
|
|
260
|
+
- Persistent cache (AsyncStorage / MMKV)
|
|
261
|
+
- Devtools panel
|
|
262
|
+
- Custom engine adapters (GraphQL, REST)
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataAccessProvider (T-16) + internal context.
|
|
3
|
+
*
|
|
4
|
+
* The provider is the single injection point for the registry, engine
|
|
5
|
+
* adapters, RequestContext factory, event bus, and TanStack QueryClient.
|
|
6
|
+
* If no bus / queryClient is supplied, one is created lazily and shared
|
|
7
|
+
* across the entire tree.
|
|
8
|
+
*
|
|
9
|
+
* See data-access/spec.md §Registry pattern.
|
|
10
|
+
*/
|
|
11
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
12
|
+
import type { ReactNode } from 'react';
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import type { EngineAdapters } from './dispatcher';
|
|
15
|
+
import type { EventBus, Registry, RequestContext } from './types';
|
|
16
|
+
/**
|
|
17
|
+
* Concrete provider props. Diverges from DataAccessProviderProps in
|
|
18
|
+
* ./types (which stays engine-agnostic to avoid a circular import) by
|
|
19
|
+
* accepting a strongly typed EngineAdapters bundle.
|
|
20
|
+
*/
|
|
21
|
+
export interface DataAccessProviderConfig {
|
|
22
|
+
registry: Registry;
|
|
23
|
+
engines: EngineAdapters;
|
|
24
|
+
context: () => RequestContext;
|
|
25
|
+
/**
|
|
26
|
+
* Optional override — when omitted the provider mints its own bus.
|
|
27
|
+
* Sharing a bus across nested providers is a v0.2.0 concern.
|
|
28
|
+
*/
|
|
29
|
+
bus?: EventBus;
|
|
30
|
+
/**
|
|
31
|
+
* Optional override — when omitted the provider mints its own
|
|
32
|
+
* QueryClient with default options. Supply your own to share a
|
|
33
|
+
* client across nested trees or to configure retry/staleTime.
|
|
34
|
+
*/
|
|
35
|
+
queryClient?: QueryClient;
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Runtime shape exposed via context. Kept internal so app code goes
|
|
40
|
+
* through hooks (useRequestContext / useAppEvent / …) — no direct
|
|
41
|
+
* consumers of the context value.
|
|
42
|
+
*/
|
|
43
|
+
export interface DataAccessRuntime {
|
|
44
|
+
registry: Registry;
|
|
45
|
+
engines: EngineAdapters;
|
|
46
|
+
bus: EventBus;
|
|
47
|
+
queryClient: QueryClient;
|
|
48
|
+
getContext: () => RequestContext;
|
|
49
|
+
}
|
|
50
|
+
export declare function DataAccessProvider(props: DataAccessProviderConfig): React.ReactElement;
|
|
51
|
+
/**
|
|
52
|
+
* Internal — used by every hook to reach the runtime. Throws a typed
|
|
53
|
+
* error when a hook is used outside a DataAccessProvider.
|
|
54
|
+
*/
|
|
55
|
+
export declare function useDataAccessRuntime(): DataAccessRuntime;
|
|
56
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAuB,MAAM,uBAAuB,CAAA;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,OAAO,KAAK,EACV,QAAQ,EACR,QAAQ,EACR,cAAc,EACf,MAAM,SAAS,CAAA;AAEhB;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,MAAM,cAAc,CAAA;IAC7B;;;OAGG;IACH,GAAG,CAAC,EAAE,QAAQ,CAAA;IACd;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,GAAG,EAAE,QAAQ,CAAA;IACb,WAAW,EAAE,WAAW,CAAA;IACxB,UAAU,EAAE,MAAM,cAAc,CAAA;CACjC;AAMD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,wBAAwB,GAC9B,KAAK,CAAC,YAAY,CAyCpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CASxD"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* DataAccessProvider (T-16) + internal context.
|
|
4
|
+
*
|
|
5
|
+
* The provider is the single injection point for the registry, engine
|
|
6
|
+
* adapters, RequestContext factory, event bus, and TanStack QueryClient.
|
|
7
|
+
* If no bus / queryClient is supplied, one is created lazily and shared
|
|
8
|
+
* across the entire tree.
|
|
9
|
+
*
|
|
10
|
+
* See data-access/spec.md §Registry pattern.
|
|
11
|
+
*/
|
|
12
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import { DataAccessError } from './errors';
|
|
15
|
+
import { createEventBus } from './invalidation';
|
|
16
|
+
const DataAccessContext = React.createContext(null);
|
|
17
|
+
DataAccessContext.displayName = 'DataAccessContext';
|
|
18
|
+
export function DataAccessProvider(props) {
|
|
19
|
+
const { registry, engines, context, bus: suppliedBus, queryClient: suppliedClient, children, } = props;
|
|
20
|
+
// Bus / client are created once per provider mount. Deliberately not
|
|
21
|
+
// memoized on the supplied identity — swapping either mid-lifetime is
|
|
22
|
+
// unsupported.
|
|
23
|
+
const busRef = React.useRef(null);
|
|
24
|
+
if (busRef.current === null) {
|
|
25
|
+
busRef.current = suppliedBus ?? createEventBus();
|
|
26
|
+
}
|
|
27
|
+
const clientRef = React.useRef(null);
|
|
28
|
+
if (clientRef.current === null) {
|
|
29
|
+
clientRef.current = suppliedClient ?? new QueryClient();
|
|
30
|
+
}
|
|
31
|
+
const runtime = React.useMemo(() => ({
|
|
32
|
+
registry,
|
|
33
|
+
engines,
|
|
34
|
+
bus: busRef.current,
|
|
35
|
+
queryClient: clientRef.current,
|
|
36
|
+
getContext: context,
|
|
37
|
+
}), [registry, engines, context]);
|
|
38
|
+
return (_jsx(DataAccessContext.Provider, { value: runtime, children: _jsx(QueryClientProvider, { client: runtime.queryClient, children: children }) }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Internal — used by every hook to reach the runtime. Throws a typed
|
|
42
|
+
* error when a hook is used outside a DataAccessProvider.
|
|
43
|
+
*/
|
|
44
|
+
export function useDataAccessRuntime() {
|
|
45
|
+
const runtime = React.useContext(DataAccessContext);
|
|
46
|
+
if (runtime === null) {
|
|
47
|
+
throw new DataAccessError('engine_missing', 'data-access hook called outside <DataAccessProvider>');
|
|
48
|
+
}
|
|
49
|
+
return runtime;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAExE,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AA2C/C,MAAM,iBAAiB,GAAG,KAAK,CAAC,aAAa,CAA2B,IAAI,CAAC,CAAA;AAE7E,iBAAiB,CAAC,WAAW,GAAG,mBAAmB,CAAA;AAEnD,MAAM,UAAU,kBAAkB,CAChC,KAA+B;IAE/B,MAAM,EACJ,QAAQ,EACR,OAAO,EACP,OAAO,EACP,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,cAAc,EAC3B,QAAQ,GACT,GAAG,KAAK,CAAA;IAET,qEAAqE;IACrE,sEAAsE;IACtE,eAAe;IACf,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAkB,IAAI,CAAC,CAAA;IAClD,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,CAAC,OAAO,GAAG,WAAW,IAAI,cAAc,EAAE,CAAA;IAClD,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAqB,IAAI,CAAC,CAAA;IACxD,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC/B,SAAS,CAAC,OAAO,GAAG,cAAc,IAAI,IAAI,WAAW,EAAE,CAAA;IACzD,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAC3B,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ;QACR,OAAO;QACP,GAAG,EAAE,MAAM,CAAC,OAAmB;QAC/B,WAAW,EAAE,SAAS,CAAC,OAAsB;QAC7C,UAAU,EAAE,OAAO;KACpB,CAAC,EACF,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAC7B,CAAA;IAED,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,OAAO,YACxC,KAAC,mBAAmB,IAAC,MAAM,EAAE,OAAO,CAAC,WAAW,YAC7C,QAAQ,GACW,GACK,CAC9B,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAA;IACnD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CACvB,gBAAgB,EAChB,sDAAsD,CACvD,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
package/dist/define.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry primitives — defineQuery / defineMutation / defineEvent.
|
|
3
|
+
*
|
|
4
|
+
* The Define API is entirely local: no engine access, no React, no
|
|
5
|
+
* dispatcher. It hands back typed, discriminated handles that the
|
|
6
|
+
* dispatcher (arriving in a later task) can safely inspect and route.
|
|
7
|
+
*
|
|
8
|
+
* See data-access/spec.md §Define API.
|
|
9
|
+
*/
|
|
10
|
+
import type { EventDefinition, MutationConfig, MutationDefinition, QueryConfig, QueryDefinition } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* Register a typed query. Runtime enforces the engine-specific required
|
|
13
|
+
* field so JS callers get a loud error even when TypeScript is skipped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function defineQuery<TInput, TOutput>(config: QueryConfig<TInput, TOutput>): QueryDefinition<TInput, TOutput>;
|
|
16
|
+
/**
|
|
17
|
+
* Register a typed mutation. `exec` is required; `invalidates` patterns
|
|
18
|
+
* fire through the invalidation bus on success.
|
|
19
|
+
*/
|
|
20
|
+
export declare function defineMutation<TInput, TOutput>(config: MutationConfig<TInput, TOutput>): MutationDefinition<TInput, TOutput>;
|
|
21
|
+
/**
|
|
22
|
+
* Register a typed event. Payload type is inferred at the point of use
|
|
23
|
+
* (via useAppEvent). No runtime state is captured here.
|
|
24
|
+
*/
|
|
25
|
+
export declare function defineEvent<TPayload>(): EventDefinition<TPayload>;
|
|
26
|
+
//# sourceMappingURL=define.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,eAAe,EAChB,MAAM,SAAS,CAAA;AAEhB;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,EACzC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAGlC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAC5C,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,GACtC,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAQrC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,KAAK,eAAe,CAAC,QAAQ,CAAC,CAEjE"}
|
package/dist/define.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry primitives — defineQuery / defineMutation / defineEvent.
|
|
3
|
+
*
|
|
4
|
+
* The Define API is entirely local: no engine access, no React, no
|
|
5
|
+
* dispatcher. It hands back typed, discriminated handles that the
|
|
6
|
+
* dispatcher (arriving in a later task) can safely inspect and route.
|
|
7
|
+
*
|
|
8
|
+
* See data-access/spec.md §Define API.
|
|
9
|
+
*/
|
|
10
|
+
import { DataAccessError } from './errors';
|
|
11
|
+
/**
|
|
12
|
+
* Register a typed query. Runtime enforces the engine-specific required
|
|
13
|
+
* field so JS callers get a loud error even when TypeScript is skipped.
|
|
14
|
+
*/
|
|
15
|
+
export function defineQuery(config) {
|
|
16
|
+
assertQueryEngineField(config);
|
|
17
|
+
return { __kind: 'query', config };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Register a typed mutation. `exec` is required; `invalidates` patterns
|
|
21
|
+
* fire through the invalidation bus on success.
|
|
22
|
+
*/
|
|
23
|
+
export function defineMutation(config) {
|
|
24
|
+
if (typeof config.exec !== 'function') {
|
|
25
|
+
throw new DataAccessError('define_config_invalid', 'defineMutation requires an `exec` function');
|
|
26
|
+
}
|
|
27
|
+
return { __kind: 'mutation', config };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Register a typed event. Payload type is inferred at the point of use
|
|
31
|
+
* (via useAppEvent). No runtime state is captured here.
|
|
32
|
+
*/
|
|
33
|
+
export function defineEvent() {
|
|
34
|
+
return { __kind: 'event' };
|
|
35
|
+
}
|
|
36
|
+
function assertQueryEngineField(config) {
|
|
37
|
+
switch (config.engine) {
|
|
38
|
+
case 'duckdb':
|
|
39
|
+
if (typeof config.sql !== 'string' || config.sql.length === 0) {
|
|
40
|
+
throw new DataAccessError('define_config_invalid', "defineQuery: engine 'duckdb' requires a non-empty `sql` string");
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
case 'rxdb':
|
|
44
|
+
if (typeof config.query !== 'function') {
|
|
45
|
+
throw new DataAccessError('define_config_invalid', "defineQuery: engine 'rxdb' requires a `query` function");
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
case 'kv':
|
|
49
|
+
if (typeof config.keyBuilder !== 'function') {
|
|
50
|
+
throw new DataAccessError('define_config_invalid', "defineQuery: engine 'kv' requires a `keyBuilder` function");
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
default: {
|
|
54
|
+
// Exhaustiveness: if a new engine is added, TS surfaces this at compile time.
|
|
55
|
+
const exhaustive = config;
|
|
56
|
+
throw new DataAccessError('define_config_invalid', `defineQuery: unknown engine ${JSON.stringify(exhaustive)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=define.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAS1C;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,MAAoC;IAEpC,sBAAsB,CAAC,MAAM,CAAC,CAAA;IAC9B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAuC;IAEvC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACtC,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,4CAA4C,CAC7C,CAAA;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAA;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;AAC5B,CAAC;AAED,SAAS,sBAAsB,CAC7B,MAAoC;IAEpC,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,QAAQ;YACX,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9D,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,gEAAgE,CACjE,CAAA;YACH,CAAC;YACD,OAAM;QACR,KAAK,MAAM;YACT,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACvC,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,wDAAwD,CACzD,CAAA;YACH,CAAC;YACD,OAAM;QACR,KAAK,IAAI;YACP,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBAC5C,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,2DAA2D,CAC5D,CAAA;YACH,CAAC;YACD,OAAM;QACR,OAAO,CAAC,CAAC,CAAC;YACR,8EAA8E;YAC9E,MAAM,UAAU,GAAU,MAAM,CAAA;YAChC,MAAM,IAAI,eAAe,CACvB,uBAAuB,EACvB,+BAA+B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAC5D,CAAA;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine dispatcher (T-06 · T-07 · T-08).
|
|
3
|
+
*
|
|
4
|
+
* `executeQuery` resolves a registered query definition against a set of
|
|
5
|
+
* engine adapters and returns typed output. This is the read-path used
|
|
6
|
+
* both by useAppQuery (arriving in a later task) and by direct callers
|
|
7
|
+
* that want dispatcher semantics without React.
|
|
8
|
+
*
|
|
9
|
+
* Concerns wired here:
|
|
10
|
+
*
|
|
11
|
+
* - T-06 — dispatch by `def.config.engine`
|
|
12
|
+
* - T-07 — run `authorize` gate before execution
|
|
13
|
+
* - T-08 — merge tenant pair into DuckDB params
|
|
14
|
+
*
|
|
15
|
+
* Engines are provided as adapter objects (see EngineAdapters below).
|
|
16
|
+
* The dispatcher never imports @mongrov/analytics or @mongrov/db.
|
|
17
|
+
*/
|
|
18
|
+
import type { QueryDefinition, RequestContext, RxdbQueryConfig } from './types';
|
|
19
|
+
/**
|
|
20
|
+
* DuckDB adapter — analytics engine surface consumed by the dispatcher.
|
|
21
|
+
*/
|
|
22
|
+
export interface DuckdbEngine {
|
|
23
|
+
execute(sql: string, params: Record<string, unknown>): Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* RxDB adapter — the app-provided runner takes the raw QueryConfig and
|
|
27
|
+
* caller input, executes `config.query(db, input)`, converts the returned
|
|
28
|
+
* Observable to a promise (`firstValueFrom`), and yields the first value.
|
|
29
|
+
*
|
|
30
|
+
* This keeps rxjs out of the data-access peer graph — the adapter is
|
|
31
|
+
* responsible for the rxjs boundary.
|
|
32
|
+
*/
|
|
33
|
+
export interface RxdbEngine {
|
|
34
|
+
db: unknown;
|
|
35
|
+
execute<TInput>(config: RxdbQueryConfig<TInput, unknown>, input: TInput): Promise<unknown>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* KV adapter — the dispatcher builds the key via `config.keyBuilder(input)`
|
|
39
|
+
* and reads through the app-provided store.
|
|
40
|
+
*/
|
|
41
|
+
export interface KvEngine {
|
|
42
|
+
get(key: string): Promise<unknown> | unknown;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Concrete engine bundle. Any subset may be omitted; the dispatcher
|
|
46
|
+
* throws `engine_missing` if a query targets an omitted engine.
|
|
47
|
+
*/
|
|
48
|
+
export interface EngineAdapters {
|
|
49
|
+
duckdb?: DuckdbEngine;
|
|
50
|
+
rxdb?: RxdbEngine;
|
|
51
|
+
kv?: KvEngine;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* T-06 core — resolve a registered query and return typed output.
|
|
55
|
+
*/
|
|
56
|
+
export declare function executeQuery<TInput, TOutput>(def: QueryDefinition<TInput, TOutput>, input: TInput, ctx: RequestContext, engines: EngineAdapters): Promise<TOutput>;
|
|
57
|
+
/**
|
|
58
|
+
* T-07 — authorization gate. Runs the definition's `authorize` if
|
|
59
|
+
* present; a `false` result throws `AuthorizationError`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runAuthorize<TInput>(authorize: ((input: TInput, ctx: RequestContext) => boolean | Promise<boolean>) | undefined, input: TInput, ctx: RequestContext): Promise<void>;
|
|
62
|
+
//# sourceMappingURL=dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,OAAO,KAAK,EAGV,eAAe,EACf,cAAc,EACd,eAAe,EAChB,MAAM,SAAS,CAAA;AAEhB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACxE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAA;IACX,OAAO,CAAC,MAAM,EACZ,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,EAAE,CAAC,EAAE,QAAQ,CAAA;CACd;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,OAAO,EAChD,GAAG,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CAQlB;AAiCD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EACvC,SAAS,EACL,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,GACpE,SAAS,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine dispatcher (T-06 · T-07 · T-08).
|
|
3
|
+
*
|
|
4
|
+
* `executeQuery` resolves a registered query definition against a set of
|
|
5
|
+
* engine adapters and returns typed output. This is the read-path used
|
|
6
|
+
* both by useAppQuery (arriving in a later task) and by direct callers
|
|
7
|
+
* that want dispatcher semantics without React.
|
|
8
|
+
*
|
|
9
|
+
* Concerns wired here:
|
|
10
|
+
*
|
|
11
|
+
* - T-06 — dispatch by `def.config.engine`
|
|
12
|
+
* - T-07 — run `authorize` gate before execution
|
|
13
|
+
* - T-08 — merge tenant pair into DuckDB params
|
|
14
|
+
*
|
|
15
|
+
* Engines are provided as adapter objects (see EngineAdapters below).
|
|
16
|
+
* The dispatcher never imports @mongrov/analytics or @mongrov/db.
|
|
17
|
+
*/
|
|
18
|
+
import { AuthorizationError, DataAccessError, } from './errors';
|
|
19
|
+
import { mergeTenantParams } from './tenant';
|
|
20
|
+
/**
|
|
21
|
+
* T-06 core — resolve a registered query and return typed output.
|
|
22
|
+
*/
|
|
23
|
+
export async function executeQuery(def, input, ctx, engines) {
|
|
24
|
+
const parsedInput = parseInput(def.config.input, input);
|
|
25
|
+
await runAuthorize(def.config.authorize, parsedInput, ctx);
|
|
26
|
+
const raw = await dispatchByEngine(def.config, parsedInput, ctx, engines);
|
|
27
|
+
return parseOutput(def.config.output, raw);
|
|
28
|
+
}
|
|
29
|
+
function parseInput(schema, input) {
|
|
30
|
+
if (!schema)
|
|
31
|
+
return input;
|
|
32
|
+
const result = schema.safeParse(input);
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
throw new DataAccessError('zod_parse_failed', `input failed schema validation: ${result.error.message}`, result.error);
|
|
35
|
+
}
|
|
36
|
+
return result.data;
|
|
37
|
+
}
|
|
38
|
+
function parseOutput(schema, raw) {
|
|
39
|
+
const result = schema.safeParse(raw);
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
throw new DataAccessError('zod_parse_failed', `output failed schema validation: ${result.error.message}`, result.error);
|
|
42
|
+
}
|
|
43
|
+
return result.data;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* T-07 — authorization gate. Runs the definition's `authorize` if
|
|
47
|
+
* present; a `false` result throws `AuthorizationError`.
|
|
48
|
+
*/
|
|
49
|
+
export async function runAuthorize(authorize, input, ctx) {
|
|
50
|
+
if (!authorize)
|
|
51
|
+
return;
|
|
52
|
+
const allowed = await Promise.resolve(authorize(input, ctx));
|
|
53
|
+
if (!allowed) {
|
|
54
|
+
throw new AuthorizationError('authorization denied by query authorize hook');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function dispatchByEngine(config, input, ctx, engines) {
|
|
58
|
+
switch (config.engine) {
|
|
59
|
+
case 'duckdb':
|
|
60
|
+
return dispatchDuckdb(config, input, ctx, engines.duckdb);
|
|
61
|
+
case 'rxdb':
|
|
62
|
+
return dispatchRxdb(config, input, engines.rxdb);
|
|
63
|
+
case 'kv':
|
|
64
|
+
return dispatchKv(config, input, engines.kv);
|
|
65
|
+
default: {
|
|
66
|
+
const exhaustive = config;
|
|
67
|
+
throw new DataAccessError('engine_missing', `unknown engine: ${JSON.stringify(exhaustive)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function dispatchDuckdb(config, input, ctx, engine) {
|
|
72
|
+
if (!engine) {
|
|
73
|
+
throw new DataAccessError('engine_missing', "engine 'duckdb' is not wired in this dispatcher");
|
|
74
|
+
}
|
|
75
|
+
const params = mergeTenantParams(input, ctx);
|
|
76
|
+
return engine.execute(config.sql, params);
|
|
77
|
+
}
|
|
78
|
+
function dispatchRxdb(config, input, engine) {
|
|
79
|
+
if (!engine) {
|
|
80
|
+
throw new DataAccessError('engine_missing', "engine 'rxdb' is not wired in this dispatcher");
|
|
81
|
+
}
|
|
82
|
+
return engine.execute(config, input);
|
|
83
|
+
}
|
|
84
|
+
async function dispatchKv(config, input, engine) {
|
|
85
|
+
if (!engine) {
|
|
86
|
+
throw new DataAccessError('engine_missing', "engine 'kv' is not wired in this dispatcher");
|
|
87
|
+
}
|
|
88
|
+
const key = config.keyBuilder(input);
|
|
89
|
+
return engine.get(key);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"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"}
|