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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 0 additions & 10 deletions examples/prisma-next-demo/src/prisma/contract.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions examples/prisma-next-demo/src/prisma/contract.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/prisma-next-demo/test/demo-dx.types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import contractJson from '../src/prisma/contract.json' with { type: 'json' };
test('contract.d.ts exports Contract and TypeMaps separately', () => {
expectTypeOf<Contract>().toHaveProperty('models');
expectTypeOf<TypeMaps>().toHaveProperty('codecTypes');
expectTypeOf<TypeMaps>().toHaveProperty('operationTypes');
expectTypeOf<TypeMaps>().toHaveProperty('queryOperationTypes');
});

test('validateContract<Contract> output is assignable to visualization shape', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export interface ControlStack<
readonly extensionPacks: readonly ControlExtensionDescriptor<TFamilyId, TTargetId>[];

readonly codecTypeImports: ReadonlyArray<TypesImportSpec>;
readonly operationTypeImports: ReadonlyArray<TypesImportSpec>;
readonly queryOperationTypeImports: ReadonlyArray<TypesImportSpec>;
readonly extensionIds: ReadonlyArray<string>;
readonly codecLookup: CodecLookup;
Expand Down Expand Up @@ -101,21 +100,6 @@ export function extractCodecTypeImports(
return imports;
}

export function extractOperationTypeImports(
descriptors: ReadonlyArray<Pick<ComponentMetadata, 'types'>>,
): ReadonlyArray<TypesImportSpec> {
const imports: TypesImportSpec[] = [];

for (const descriptor of descriptors) {
const operationTypes = descriptor.types?.operationTypes;
if (operationTypes?.import) {
imports.push(operationTypes.import);
}
}

return imports;
}

export function extractQueryOperationTypeImports(
descriptors: ReadonlyArray<Pick<ComponentMetadata, 'types'>>,
): ReadonlyArray<TypesImportSpec> {
Expand Down Expand Up @@ -382,7 +366,6 @@ export function createControlStack<TFamilyId extends string, TTargetId extends s
extensionPacks: extensionPacks as readonly ControlExtensionDescriptor<TFamilyId, TTargetId>[],

codecTypeImports: extractCodecTypeImports(allDescriptors),
operationTypeImports: extractOperationTypeImports(allDescriptors),
queryOperationTypeImports: extractQueryOperationTypeImports(allDescriptors),
extensionIds: extractComponentIds(family, target, adapter, extensionPacks),
codecLookup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export interface GenerateContractTypesOptions {

export interface ValidationContext {
readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
readonly extensionIds?: ReadonlyArray<string>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export {
extractCodecLookup,
extractCodecTypeImports,
extractComponentIds,
extractOperationTypeImports,
extractQueryOperationTypeImports,
} from '../control/control-stack';
export type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export interface ComponentMetadata {
*/
readonly codecInstances?: ReadonlyArray<Codec>;
};
readonly operationTypes?: { readonly import: TypesImportSpec };
readonly queryOperationTypes?: { readonly import: TypesImportSpec };
readonly storage?: ReadonlyArray<{
readonly typeId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
extractCodecLookup,
extractCodecTypeImports,
extractComponentIds,
extractOperationTypeImports,
extractQueryOperationTypeImports,
validateScalarTypeCodecIds,
} from '../src/control/control-stack';
Expand Down Expand Up @@ -78,26 +77,6 @@ describe('extractCodecTypeImports', () => {
});
});

describe('extractOperationTypeImports', () => {
it('returns empty array for descriptors without operation types', () => {
const result = extractOperationTypeImports([createDescriptor()]);
expect(result).toEqual([]);
});

it('extracts operation type import', () => {
const result = extractOperationTypeImports([
createDescriptor({
types: {
operationTypes: {
import: { package: '@test/ops', named: 'Ops', alias: 'O' },
},
},
}),
]);
expect(result).toEqual([{ package: '@test/ops', named: 'Ops', alias: 'O' }]);
});
});

describe('extractQueryOperationTypeImports', () => {
it('returns empty array for descriptors without query operation types', () => {
const result = extractQueryOperationTypeImports([createDescriptor()]);
Expand Down Expand Up @@ -371,9 +350,6 @@ describe('createControlStack', () => {
codecTypes: {
typeImports: [{ package: '@test/param', named: 'P', alias: 'TP' }],
},
operationTypes: {
import: { package: '@test/ops', named: 'O', alias: 'TO' },
},
queryOperationTypes: {
import: { package: '@test/qops', named: 'Q', alias: 'TQ' },
},
Expand All @@ -392,7 +368,6 @@ describe('createControlStack', () => {
);

expect(state.codecTypeImports).toHaveLength(2);
expect(state.operationTypeImports).toHaveLength(1);
expect(state.queryOperationTypeImports).toHaveLength(1);
expect(state.extensionIds).toEqual(['sql', 'target', 'adapter']);
expect(Object.keys(state.authoringContributions.type)).toEqual(['myType']);
Expand Down Expand Up @@ -435,7 +410,6 @@ describe('createControlStack', () => {

expect(state.codecTypeImports).toHaveLength(1);
expect(state.extensionIds).toEqual(['mongo']);
expect(state.operationTypeImports).toEqual([]);
});

it('returns empty state when descriptors have no types', () => {
Expand All @@ -446,7 +420,6 @@ describe('createControlStack', () => {
}),
);
expect(state.codecTypeImports).toEqual([]);
expect(state.operationTypeImports).toEqual([]);
expect(state.queryOperationTypeImports).toEqual([]);
expect(state.extensionIds).toEqual(['fam', 'tgt']);
expect(state.authoringContributions).toEqual({ field: {}, type: {} });
Expand Down
23 changes: 11 additions & 12 deletions packages/1-framework/1-core/operations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ This package provides a generic, target-neutral operation registry. It's part of
## Responsibilities

- **Operation Registry**: Generic operation registry interface and implementation
- `OperationRegistry<T>`: Generic interface for registering and iterating operations, parameterized by entry type
- `OperationRegistry<T>`: Generic interface for registering and iterating operations, parameterized by entry type. `register(name, descriptor)` keys each entry by an explicit method name supplied at the call site.
- `createOperationRegistry<T>()`: Factory function to create operation registries
- `OperationEntry`: Base entry type with `args` and `returns`
- `OperationDescriptor<T>`: Entry plus a `method` name, used for registration
- `OperationEntry`: Base entry type with `self` and `impl`
- `OperationDescriptor<T>`: Alias for the entry shape used at registration sites
- `OperationDescriptors<T>`: `Readonly<Record<string, OperationDescriptor<T>>>` — the natural shape contributors return, where the record key IS the method name
- `ParamSpec`: Describes an operation parameter (`codecId`, `nullable`), used for both arguments and return values

## Dependencies
Expand Down Expand Up @@ -59,30 +60,28 @@ import { createOperationRegistry, type OperationDescriptor } from '@prisma-next/
const registry = createOperationRegistry();

const descriptor: OperationDescriptor = {
method: 'cosineDistance',
args: [{ codecId: 'pg/vector@1', nullable: false }],
returns: { codecId: 'pg/float8@1', nullable: false },
self: { codecId: 'pg/vector@1' },
impl: () => ({ returnType: { codecId: 'pg/float8@1', nullable: false } }),
};

registry.register(descriptor);
registry.register('cosineDistance', descriptor);
const entries = registry.entries(); // Record<string, OperationEntry>
```

### Using a Custom Entry Type

```typescript
import { createOperationRegistry, type OperationEntry, type OperationDescriptor } from '@prisma-next/operations';
import { createOperationRegistry, type OperationEntry } from '@prisma-next/operations';

interface MyEntry extends OperationEntry {
readonly extra: string;
}

const registry = createOperationRegistry<MyEntry>();

registry.register({
method: 'myMethod',
args: [],
returns: { codecId: 'pg/int4@1', nullable: false },
registry.register('myMethod', {
self: { codecId: 'pg/int4@1' },
impl: () => undefined as never,
extra: 'custom data',
});
```
Expand Down
25 changes: 12 additions & 13 deletions packages/1-framework/1-core/operations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export interface OperationEntry {
readonly impl: (...args: never[]) => unknown;
}

export type OperationDescriptor<T extends OperationEntry = OperationEntry> = T & {
readonly method: string;
};
export type OperationDescriptor<T extends OperationEntry = OperationEntry> = T;

export type OperationDescriptors<T extends OperationEntry = OperationEntry> = Readonly<
Record<string, OperationDescriptor<T>>
>;

export interface OperationRegistry<T extends OperationEntry = OperationEntry> {
register(descriptor: OperationDescriptor<T>): void;
register(name: string, descriptor: OperationDescriptor<T>): void;
entries(): Readonly<Record<string, T>>;
}

Expand All @@ -33,24 +35,21 @@ export function createOperationRegistry<
const operations: Record<string, T> = Object.create(null);

return {
register(descriptor: OperationDescriptor<T>) {
if (descriptor.method in operations) {
throw new Error(`Operation "${descriptor.method}" is already registered`);
register(name: string, descriptor: OperationDescriptor<T>) {
if (name in operations) {
throw new Error(`Operation "${name}" is already registered`);
}
if (descriptor.self) {
const hasCodecId = descriptor.self.codecId !== undefined;
const hasTraits = descriptor.self.traits !== undefined && descriptor.self.traits.length > 0;
if (!hasCodecId && !hasTraits) {
throw new Error(`Operation "${descriptor.method}" self has neither codecId nor traits`);
throw new Error(`Operation "${name}" self has neither codecId nor traits`);
}
if (hasCodecId && hasTraits) {
throw new Error(`Operation "${descriptor.method}" self has both codecId and traits`);
throw new Error(`Operation "${name}" self has both codecId and traits`);
}
}
const { method: _method, ...entry } = descriptor;
// OperationDescriptor<T> = T & { method }, so stripping method yields T.
// TypeScript can't prove Omit<T & { method }, 'method'> = T for generic T.
operations[descriptor.method] = entry as unknown as T;
operations[name] = descriptor;
},
entries() {
return Object.freeze({ ...operations });
Expand Down
Loading
Loading