Skip to content

Commit c1f13f4

Browse files
committed
refactor(provider): enhance provider type inference and streamline exports
1 parent d4ec179 commit c1f13f4

File tree

6 files changed

+52
-51
lines changed

6 files changed

+52
-51
lines changed

apps/tab-manager/src/lib/epicenter/background.workspace.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,6 @@ export const backgroundWorkspace = defineWorkspace({
342342
},
343343
},
344344
exports: ({ tables, providers }) => {
345-
// Type assertions for provider exports
346-
// TypeScript can't infer these through the generic chain
347-
const persistence = providers.persistence as {
348-
whenSynced: Promise<IndexeddbPersistence>;
349-
};
350-
const chromeSync = providers.chromeSync as {
351-
syncAllFromChrome: () => Promise<void>;
352-
};
353-
354345
return {
355346
/**
356347
* Get the tables for direct access.
@@ -361,14 +352,14 @@ export const backgroundWorkspace = defineWorkspace({
361352
* Wait for IndexedDB to finish initial sync.
362353
*/
363354
get whenSynced() {
364-
return persistence.whenSynced;
355+
return providers.persistence.whenSynced;
365356
},
366357

367358
/**
368359
* Perform a full sync from Chrome to Y.Doc.
369360
* Clears existing data and re-syncs all tabs/windows.
370361
*/
371-
syncAllFromChrome: chromeSync.syncAllFromChrome,
362+
syncAllFromChrome: providers.chromeSync.syncAllFromChrome,
372363

373364
/**
374365
* Get all tabs sorted by index.

packages/epicenter/src/core/provider.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,31 @@ export type Provider<
123123
*/
124124
export type WorkspaceProviderMap = Record<string, ProviderExports>;
125125

126+
/**
127+
* Infer the exports type from a provider function.
128+
*
129+
* Extracts the return type from a provider, unwrapping Promise and excluding void.
130+
* Returns `Record<string, never>` (empty object) for providers that return void.
131+
*
132+
* @example
133+
* ```typescript
134+
* // Provider that returns exports
135+
* const myProvider = ({ ydoc }) => defineProviderExports({ db: sqlite });
136+
* type Exports = InferProviderExports<typeof myProvider>;
137+
* // Exports = { db: typeof sqlite }
138+
*
139+
* // Provider that returns void (side effects only)
140+
* const sideEffectProvider = ({ ydoc }) => { ydoc.on('update', ...); };
141+
* type Exports = InferProviderExports<typeof sideEffectProvider>;
142+
* // Exports = Record<string, never>
143+
* ```
144+
*/
145+
export type InferProviderExports<P> = P extends (context: any) => infer R
146+
? Awaited<R> extends void | undefined
147+
? Record<string, never>
148+
: Exclude<Awaited<R>, void | undefined>
149+
: Record<string, never>;
150+
126151
/**
127152
* Define provider exports with type safety (identity function).
128153
*

packages/epicenter/src/core/workspace/client.browser.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import * as Y from 'yjs';
1313
import type { WorkspaceActionMap, WorkspaceExports } from '../actions';
1414
import { createEpicenterDb } from '../db/core';
15-
import type { ProviderExports, WorkspaceProviderMap } from '../provider';
15+
import type { Provider, ProviderExports } from '../provider';
1616
import type { WorkspaceSchema } from '../schema';
1717
import { createWorkspaceValidators } from '../schema';
1818
import type { WorkspaceClient, WorkspacesToClients } from './client.shared';
@@ -396,16 +396,10 @@ export function createWorkspaceClient<
396396
const TDeps extends readonly AnyWorkspaceConfig[],
397397
const TId extends string,
398398
TWorkspaceSchema extends WorkspaceSchema,
399-
const TProviderResults extends WorkspaceProviderMap,
399+
const TProviders extends Record<string, Provider<TWorkspaceSchema>>,
400400
TExports extends WorkspaceExports,
401401
>(
402-
workspace: WorkspaceConfig<
403-
TDeps,
404-
TId,
405-
TWorkspaceSchema,
406-
TProviderResults,
407-
TExports
408-
>,
402+
workspace: WorkspaceConfig<TDeps, TId, TWorkspaceSchema, TProviders, TExports>,
409403
): WorkspaceClient<TExports> {
410404
// Collect all workspace configs (target + dependencies) for flat/hoisted initialization
411405
const allWorkspaceConfigs: WorkspaceConfig[] = [];

packages/epicenter/src/core/workspace/client.node.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import path from 'node:path';
1212
import * as Y from 'yjs';
1313
import type { WorkspaceActionMap, WorkspaceExports } from '../actions';
1414
import { createEpicenterDb } from '../db/core';
15-
import type { ProviderExports, WorkspaceProviderMap } from '../provider';
15+
import type { Provider, ProviderExports } from '../provider';
1616
import type { WorkspaceSchema } from '../schema';
1717
import { createWorkspaceValidators } from '../schema';
1818
import type { EpicenterDir, StorageDir } from '../types';
@@ -395,16 +395,10 @@ export async function createWorkspaceClient<
395395
const TDeps extends readonly AnyWorkspaceConfig[],
396396
const TId extends string,
397397
TWorkspaceSchema extends WorkspaceSchema,
398-
const TProviderResults extends WorkspaceProviderMap,
398+
const TProviders extends Record<string, Provider<TWorkspaceSchema>>,
399399
TExports extends WorkspaceExports,
400400
>(
401-
workspace: WorkspaceConfig<
402-
TDeps,
403-
TId,
404-
TWorkspaceSchema,
405-
TProviderResults,
406-
TExports
407-
>,
401+
workspace: WorkspaceConfig<TDeps, TId, TWorkspaceSchema, TProviders, TExports>,
408402
): Promise<WorkspaceClient<TExports>> {
409403
// Collect all workspace configs (target + dependencies) for flat/hoisted initialization
410404
const allWorkspaceConfigs: WorkspaceConfig[] = [];

packages/epicenter/src/core/workspace/config.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import type { WorkspaceExports } from '../actions';
22
import type { WorkspaceBlobs } from '../blobs';
33
import type { Tables } from '../db/core';
4-
import type {
5-
Provider,
6-
ProviderExports,
7-
WorkspaceProviderMap,
8-
} from '../provider';
4+
import type { InferProviderExports, Provider } from '../provider';
95
import type { WorkspaceSchema, WorkspaceValidators } from '../schema';
106
import type { EpicenterDir, StorageDir } from '../types';
117

@@ -103,17 +99,11 @@ export function defineWorkspace<
10399
const TDeps extends readonly AnyWorkspaceConfig[],
104100
const TId extends string,
105101
TWorkspaceSchema extends WorkspaceSchema,
106-
const TProviderResults extends WorkspaceProviderMap,
102+
const TProviders extends Record<string, Provider<TWorkspaceSchema>>,
107103
TExports extends WorkspaceExports,
108104
>(
109-
workspace: WorkspaceConfig<
110-
TDeps,
111-
TId,
112-
TWorkspaceSchema,
113-
TProviderResults,
114-
TExports
115-
>,
116-
): WorkspaceConfig<TDeps, TId, TWorkspaceSchema, TProviderResults, TExports> {
105+
workspace: WorkspaceConfig<TDeps, TId, TWorkspaceSchema, TProviders, TExports>,
106+
): WorkspaceConfig<TDeps, TId, TWorkspaceSchema, TProviders, TExports> {
117107
// Validate workspace ID
118108
if (!workspace.id || typeof workspace.id !== 'string') {
119109
throw new Error('Workspace must have a valid string ID');
@@ -142,6 +132,14 @@ export function defineWorkspace<
142132
*
143133
* Fully-featured workspace configuration used for defining workspaces and their dependencies.
144134
*
135+
* ## Provider Type Inference
136+
*
137+
* The `TProviders` type parameter captures the actual provider functions you pass.
138+
* The `exports` factory receives provider exports derived via `InferProviderExports`:
139+
* - Provider returns `{ db: Db }` → exports receives `{ db: Db }`
140+
* - Provider returns `void` → exports receives `Record<string, never>` (empty object)
141+
* - Provider returns `Promise<{ db: Db }>` → exports receives `{ db: Db }` (unwrapped)
142+
*
145143
* ## Dependency Constraint
146144
*
147145
* The `dependencies` field uses `AnyWorkspaceConfig[]` as a minimal constraint.
@@ -161,18 +159,16 @@ export type WorkspaceConfig<
161159
TDeps extends readonly AnyWorkspaceConfig[] = readonly AnyWorkspaceConfig[],
162160
TId extends string = string,
163161
TWorkspaceSchema extends WorkspaceSchema = WorkspaceSchema,
164-
TProviderResults extends WorkspaceProviderMap = WorkspaceProviderMap,
162+
TProviders extends Record<string, Provider<TWorkspaceSchema>> = Record<
163+
string,
164+
Provider<TWorkspaceSchema>
165+
>,
165166
TExports extends WorkspaceExports = WorkspaceExports,
166167
> = {
167168
id: TId;
168169
tables: TWorkspaceSchema;
169170
dependencies?: TDeps;
170-
providers: {
171-
[K in keyof TProviderResults]: Provider<
172-
TWorkspaceSchema,
173-
TProviderResults[K] extends ProviderExports ? TProviderResults[K] : ProviderExports
174-
>;
175-
};
171+
providers: TProviders;
176172
/**
177173
* Factory function that creates workspace exports (actions, utilities, etc.)
178174
*
@@ -213,7 +209,7 @@ export type WorkspaceConfig<
213209
schema: TWorkspaceSchema;
214210
validators: WorkspaceValidators<TWorkspaceSchema>;
215211
workspaces: WorkspacesToExports<TDeps>;
216-
providers: TProviderResults;
212+
providers: { [K in keyof TProviders]: InferProviderExports<TProviders[K]> };
217213
blobs: WorkspaceBlobs<TWorkspaceSchema>;
218214
storageDir: StorageDir | undefined;
219215
epicenterDir: EpicenterDir | undefined;

packages/epicenter/src/index.shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export {
9797
} from './core/errors';
9898

9999
export type {
100+
InferProviderExports,
100101
Provider,
101102
ProviderContext,
102103
ProviderExports,

0 commit comments

Comments
 (0)