@prisma-next/mongo-contract 0.13.0 → 0.14.0-dev.10

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.
@@ -2,13 +2,11 @@ import type {
2
2
  Contract,
3
3
  ContractField,
4
4
  ContractModel,
5
- ContractModelDefinitions,
6
5
  ContractValueObject,
7
6
  ContractValueObjectDefinitions,
8
7
  StorageBase,
9
8
  } from '@prisma-next/contract/types';
10
- import type { Namespace } from '@prisma-next/framework-components/ir';
11
- import type { MongoCollection } from './ir/mongo-collection';
9
+ import type { Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
12
10
  import type { MongoIndexOptionsInput } from './ir/mongo-index-options';
13
11
 
14
12
  export type MongoIndexFieldValue = 1 | -1 | 'text' | '2dsphere' | '2d' | 'hashed';
@@ -59,30 +57,38 @@ export type MongoModelDefinition = ContractModel<MongoModelStorage>;
59
57
  * `namespaces` field) or a fully-constructed class instance (with
60
58
  * `namespaces`). The class structurally satisfies this shape.
61
59
  */
60
+ import type { MongoCollection } from './ir/mongo-collection';
61
+
62
+ type MongoNamespaceEntries = Readonly<Record<string, Readonly<Record<string, unknown>>>> & {
63
+ readonly collection?: Readonly<Record<string, MongoCollection>>;
64
+ };
65
+
62
66
  export type MongoStorageShape<THash extends string = string> = StorageBase<THash> & {
63
67
  readonly namespaces: Record<
64
68
  string,
65
69
  Namespace & {
66
- readonly entries: Readonly<{
67
- readonly collection: Readonly<Record<string, MongoCollection>>;
68
- }>;
70
+ readonly entries: MongoNamespaceEntries;
69
71
  }
70
72
  >;
71
73
  };
72
74
 
73
- export type MongoContract<
74
- S extends MongoStorageShape = MongoStorageShape,
75
- M extends Record<string, MongoModelDefinition> = Record<string, MongoModelDefinition>,
76
- > = Contract<S, M>;
75
+ export type MongoContract<S extends MongoStorageShape = MongoStorageShape> = Contract<S>;
77
76
 
78
- /** Model map inferred from a {@link MongoContract} (domain.namespaces union). */
79
- export type MongoModelsMap<TContract extends MongoContract> = ContractModelDefinitions<TContract>;
77
+ /**
78
+ * Model map for the contract's sole (unbound) domain namespace. Mongo is
79
+ * structurally single-namespace, so its models live under
80
+ * {@link UNBOUND_NAMESPACE_ID} rather than in a flat cross-namespace union.
81
+ * Every Mongo type that needs the model map reads it through here, so none
82
+ * indexes the contract's namespaces directly.
83
+ */
84
+ export type MongoModelsMap<TContract extends MongoContract> =
85
+ TContract['domain']['namespaces'][typeof UNBOUND_NAMESPACE_ID]['models'];
80
86
 
81
87
  export type RootModelName<
82
88
  TContract extends MongoContract,
83
89
  RootName extends keyof TContract['roots'] & string,
84
90
  > = TContract['roots'][RootName] extends { readonly model: infer M extends string }
85
- ? M & keyof import('@prisma-next/contract/types').ContractModelDefinitions<TContract>
91
+ ? M & keyof MongoModelsMap<TContract>
86
92
  : never;
87
93
 
88
94
  export type MongoTypeMaps<
@@ -132,6 +138,26 @@ export type ExtractMongoFieldInputTypes<T> =
132
138
  : Record<string, never>
133
139
  : Record<string, never>;
134
140
 
141
+ /**
142
+ * The per-model field-output map at the contract's unbound namespace. The
143
+ * framework emitter nests `FieldOutputTypes` by namespace id
144
+ * (`{ [ns]: { [model]: { [field]: <refined> } } }`); Mongo is structurally
145
+ * single-namespace, so its refined rows resolve the per-model map under
146
+ * {@link UNBOUND_NAMESPACE_ID}. A map without that coordinate (e.g. a contract
147
+ * carrying no type maps) resolves to `never`, which the row resolvers read as
148
+ * "no refined map" and fall back to codec-based inference.
149
+ */
150
+ export type MongoUnboundFieldOutputTypes<T> =
151
+ ExtractMongoFieldOutputTypes<T> extends Record<typeof UNBOUND_NAMESPACE_ID, infer Inner>
152
+ ? Inner
153
+ : never;
154
+
155
+ /** Input-side counterpart of {@link MongoUnboundFieldOutputTypes}. */
156
+ export type MongoUnboundFieldInputTypes<T> =
157
+ ExtractMongoFieldInputTypes<T> extends Record<typeof UNBOUND_NAMESPACE_ID, infer Inner>
158
+ ? Inner
159
+ : never;
160
+
135
161
  type ExtractValueObjects<TContract extends Contract> = ContractValueObjectDefinitions<TContract>;
136
162
 
137
163
  type NormalizeContractFields<TFields> = {
@@ -184,11 +210,8 @@ type InferFieldType<
184
210
 
185
211
  export type InferModelRow<
186
212
  TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
187
- ModelName extends string & keyof ContractModelDefinitions<TContract>,
188
- TFields extends Record<
189
- string,
190
- ContractField
191
- > = ContractModelDefinitions<TContract>[ModelName]['fields'],
213
+ ModelName extends string & keyof MongoModelsMap<TContract>,
214
+ TFields extends Record<string, ContractField> = MongoModelsMap<TContract>[ModelName]['fields'],
192
215
  TCodecTypes extends Record<string, { output: unknown }> = ExtractMongoCodecTypes<TContract>,
193
216
  TValueObjects extends Record<string, ContractValueObject> = ExtractValueObjects<TContract>,
194
217
  > = {
@@ -0,0 +1,34 @@
1
+ import type {
2
+ AnyEntityKindDescriptor,
3
+ EntityKindDescriptor,
4
+ } from '@prisma-next/framework-components/ir';
5
+ import { StorageCollectionSchema } from './contract-schema';
6
+ import { MongoCollection, type MongoCollectionInput } from './ir/mongo-collection';
7
+
8
+ export const collectionEntityKind: EntityKindDescriptor<MongoCollectionInput, MongoCollection> = {
9
+ kind: 'collection',
10
+ schema: StorageCollectionSchema,
11
+ construct: (input) => new MongoCollection(input),
12
+ };
13
+
14
+ /**
15
+ * Assembles the `kind → descriptor` registry for Mongo namespaces: the built-in
16
+ * `collection` kind plus any target `packKinds`. This builds the lookup table —
17
+ * it does not touch contract data. `hydrateNamespaceEntities` later consumes
18
+ * this registry to turn a namespace's raw entries into IR instances. Throws on
19
+ * a duplicate kind.
20
+ */
21
+ export function composeMongoEntityKinds(
22
+ packKinds: readonly AnyEntityKindDescriptor[] = [],
23
+ ): ReadonlyMap<string, AnyEntityKindDescriptor> {
24
+ const kinds = new Map<string, AnyEntityKindDescriptor>([['collection', collectionEntityKind]]);
25
+ for (const descriptor of packKinds) {
26
+ if (kinds.has(descriptor.kind)) {
27
+ throw new Error(
28
+ `composeMongoEntityKinds: duplicate entity kind "${descriptor.kind}" — each kind may be registered only once`,
29
+ );
30
+ }
31
+ kinds.set(descriptor.kind, descriptor);
32
+ }
33
+ return kinds;
34
+ }
@@ -0,0 +1 @@
1
+ export { collectionEntityKind, composeMongoEntityKinds } from '../entity-kinds';
@@ -25,6 +25,8 @@ export type {
25
25
  MongoStorageShape,
26
26
  MongoTypeMaps,
27
27
  MongoTypeMapsPhantomKey,
28
+ MongoUnboundFieldInputTypes,
29
+ MongoUnboundFieldOutputTypes,
28
30
  MongoWildcardProjection,
29
31
  RootModelName,
30
32
  } from '../contract-types';
@@ -32,7 +34,7 @@ export {
32
34
  defaultMongoDomainNamespaceId,
33
35
  defaultMongoStorageNamespaceId,
34
36
  } from '../default-namespace';
35
- export { buildMongoNamespace, buildMongoNamespaceMap } from '../ir/build-mongo-namespace';
37
+ export { buildMongoNamespace } from '../ir/build-mongo-namespace';
36
38
  export type { MongoChangeStreamPreAndPostImagesOptionsInput } from '../ir/mongo-change-stream-pre-and-post-images-options';
37
39
  export { MongoChangeStreamPreAndPostImagesOptions } from '../ir/mongo-change-stream-pre-and-post-images-options';
38
40
  export type {
@@ -63,7 +65,7 @@ export type { MongoIndexOptionDefaultsInput } from '../ir/mongo-index-option-def
63
65
  export { MongoIndexOptionDefaults } from '../ir/mongo-index-option-defaults';
64
66
  export type { MongoIndexOptionsInput } from '../ir/mongo-index-options';
65
67
  export { MongoIndexOptions } from '../ir/mongo-index-options';
66
- export type { MongoStorageInput } from '../ir/mongo-storage';
68
+ export type { MongoNamespace, MongoNamespaceEntries, MongoStorageInput } from '../ir/mongo-storage';
67
69
  export { MongoStorage } from '../ir/mongo-storage';
68
70
  export type {
69
71
  MongoTimeSeriesCollectionOptionsInput,
@@ -1,58 +1,51 @@
1
1
  import {
2
2
  freezeNode,
3
- type Namespace,
3
+ hydrateNamespaceEntities,
4
4
  NamespaceBase,
5
5
  UNBOUND_NAMESPACE_ID,
6
6
  } from '@prisma-next/framework-components/ir';
7
- import { blindCast, castAs } from '@prisma-next/utils/casts';
8
- import { MongoCollection } from './mongo-collection';
9
- import type { MongoNamespace, MongoNamespaceCollectionsInput } from './mongo-storage';
7
+ import { blindCast } from '@prisma-next/utils/casts';
8
+ import { composeMongoEntityKinds } from '../entity-kinds';
9
+ import type { MongoCollection } from './mongo-collection';
10
+ import type {
11
+ MongoNamespace,
12
+ MongoNamespaceCollectionsInput,
13
+ MongoNamespaceEntries,
14
+ } from './mongo-storage';
10
15
  import { MongoUnboundNamespace } from './mongo-unbound-namespace';
11
16
 
12
17
  const MONGO_NAMESPACE_KIND = 'mongo-namespace' as const;
13
18
 
14
- function isMaterializedMongoNamespace(
15
- ns: Namespace | MongoNamespaceCollectionsInput,
16
- ): ns is MongoNamespace {
17
- if (typeof ns !== 'object' || ns === null) {
18
- return false;
19
- }
20
- const proto = Object.getPrototypeOf(ns);
21
- if (proto === Object.prototype || proto === null) {
22
- return false;
23
- }
24
- return (ns as Namespace).kind === MONGO_NAMESPACE_KIND;
25
- }
26
-
27
19
  class MongoBoundNamespace extends NamespaceBase {
28
20
  declare readonly kind: string;
29
21
 
30
22
  readonly id: string;
31
- readonly entries: Readonly<{
32
- readonly collection: Readonly<Record<string, MongoCollection>>;
33
- }>;
23
+ readonly entries: MongoNamespaceEntries;
34
24
 
35
25
  static fromCollectionsInput(input: MongoNamespaceCollectionsInput): MongoNamespace {
36
- const collectionCount = Object.keys(input.entries.collection).length;
37
- if (input.id === UNBOUND_NAMESPACE_ID && collectionCount === 0) {
38
- return castAs<MongoNamespace>(MongoUnboundNamespace.instance);
26
+ const collectionMap = input.entries['collection'];
27
+ const collectionCount = collectionMap !== undefined ? Object.keys(collectionMap).length : 0;
28
+ const hasUnknownKinds = Object.keys(input.entries).some((kind) => kind !== 'collection');
29
+ if (input.id === UNBOUND_NAMESPACE_ID && collectionCount === 0 && !hasUnknownKinds) {
30
+ return MongoUnboundNamespace.instance;
39
31
  }
40
- return castAs<MongoNamespace>(new MongoBoundNamespace(input));
32
+ return new MongoBoundNamespace(input);
41
33
  }
42
34
 
43
35
  private constructor(input: MongoNamespaceCollectionsInput) {
44
36
  super();
45
37
  this.id = input.id;
46
- this.entries = Object.freeze({
47
- collection: Object.freeze(
48
- Object.fromEntries(
49
- Object.entries(input.entries.collection).map(([name, c]) => [
50
- name,
51
- c instanceof MongoCollection ? c : new MongoCollection(c),
52
- ]),
53
- ),
54
- ),
55
- });
38
+
39
+ const rawEntries: Record<string, Readonly<Record<string, unknown>>> = {
40
+ collection: {},
41
+ ...input.entries,
42
+ };
43
+ this.entries = Object.freeze(
44
+ blindCast<
45
+ MongoNamespaceEntries,
46
+ 'composeMongoEntityKinds() supplies the collection→MongoCollection descriptor, so this open-dict result holds the typed collection member MongoNamespaceEntries declares; the descriptor Map erases that per-kind Node type from the return.'
47
+ >(hydrateNamespaceEntities(rawEntries, composeMongoEntityKinds(), 'carry')),
48
+ );
56
49
  Object.defineProperty(this, 'kind', {
57
50
  value: MONGO_NAMESPACE_KIND,
58
51
  writable: false,
@@ -61,29 +54,12 @@ class MongoBoundNamespace extends NamespaceBase {
61
54
  });
62
55
  freezeNode(this);
63
56
  }
57
+
58
+ get collection(): Readonly<Record<string, MongoCollection>> {
59
+ return this.entries.collection ?? Object.freeze({});
60
+ }
64
61
  }
65
62
 
66
63
  export function buildMongoNamespace(input: MongoNamespaceCollectionsInput): MongoNamespace {
67
64
  return MongoBoundNamespace.fromCollectionsInput(input);
68
65
  }
69
-
70
- export function buildMongoNamespaceMap(
71
- namespaces: Readonly<Record<string, Namespace | MongoNamespaceCollectionsInput>>,
72
- ): Readonly<Record<string, MongoNamespace>> {
73
- return Object.fromEntries(
74
- Object.entries(namespaces).map(([nsKey, ns]) => [
75
- nsKey,
76
- isMaterializedMongoNamespace(ns)
77
- ? blindCast<
78
- MongoNamespace,
79
- 'a materialised Mongo-family namespace entry in a namespace map is a MongoNamespace'
80
- >(ns)
81
- : MongoBoundNamespace.fromCollectionsInput(
82
- blindCast<
83
- MongoNamespaceCollectionsInput,
84
- 'non-materialized Mongo namespace map entry is a MongoNamespaceCollectionsInput'
85
- >(ns),
86
- ),
87
- ]),
88
- );
89
- }
@@ -1,4 +1,5 @@
1
1
  import { freezeNode, IRNodeBase } from '@prisma-next/framework-components/ir';
2
+ import type { CollationOptions } from '@prisma-next/mongo-value/mongodb-types';
2
3
  import type { MongoIndexKey } from '../contract-types';
3
4
 
4
5
  /**
@@ -14,7 +15,7 @@ export interface MongoIndexInput {
14
15
  readonly expireAfterSeconds?: number;
15
16
  readonly partialFilterExpression?: Record<string, unknown>;
16
17
  readonly wildcardProjection?: Record<string, 0 | 1>;
17
- readonly collation?: Record<string, unknown>;
18
+ readonly collation?: CollationOptions;
18
19
  readonly weights?: Record<string, number>;
19
20
  readonly default_language?: string;
20
21
  readonly language_override?: string;
@@ -41,7 +42,7 @@ export class MongoIndex extends IRNodeBase {
41
42
  declare readonly expireAfterSeconds?: number;
42
43
  declare readonly partialFilterExpression?: Record<string, unknown>;
43
44
  declare readonly wildcardProjection?: Record<string, 0 | 1>;
44
- declare readonly collation?: Record<string, unknown>;
45
+ declare readonly collation?: CollationOptions;
45
46
  declare readonly weights?: Record<string, number>;
46
47
  declare readonly default_language?: string;
47
48
  declare readonly language_override?: string;
@@ -7,22 +7,17 @@ import {
7
7
  } from '@prisma-next/framework-components/ir';
8
8
  import type { MongoCollection, MongoCollectionInput } from './mongo-collection';
9
9
 
10
+ export type MongoNamespaceEntries = Readonly<Record<string, Readonly<Record<string, unknown>>>> & {
11
+ readonly collection?: Readonly<Record<string, MongoCollection>>;
12
+ };
13
+
10
14
  export interface MongoNamespaceCollectionsInput {
11
15
  readonly id: string;
12
- readonly entries: {
13
- readonly collection: Record<string, MongoCollection | MongoCollectionInput>;
14
- };
16
+ readonly entries: Readonly<Record<string, Readonly<Record<string, MongoCollectionInput>>>>;
15
17
  }
16
18
 
17
- // Mongo concretions store `MongoCollection` instances under
18
- // `entries.collection` (Mongo idiom — distinct from the SQL family's
19
- // `entries.table`). Narrowing the namespace map here lets target/family-
20
- // level consumers iterate collection slots and recover the concrete type
21
- // without the framework's wider `Namespace` tripping them up.
22
19
  export type MongoNamespace = Namespace & {
23
- readonly entries: Readonly<{
24
- readonly collection: Readonly<Record<string, MongoCollection>>;
25
- }>;
20
+ readonly entries: MongoNamespaceEntries;
26
21
  };
27
22
 
28
23
  export interface MongoStorageInput<THash extends string = string> {
@@ -3,15 +3,15 @@ import {
3
3
  NamespaceBase,
4
4
  UNBOUND_NAMESPACE_ID,
5
5
  } from '@prisma-next/framework-components/ir';
6
- import type { MongoCollection } from './mongo-collection';
6
+ import type { MongoNamespaceEntries } from './mongo-storage';
7
7
 
8
8
  export class MongoUnboundNamespace extends NamespaceBase {
9
9
  static readonly instance: MongoUnboundNamespace = new MongoUnboundNamespace();
10
10
 
11
11
  readonly id = UNBOUND_NAMESPACE_ID;
12
- readonly entries: Readonly<{
13
- readonly collection: Readonly<Record<string, MongoCollection>>;
14
- }> = Object.freeze({ collection: Object.freeze({}) });
12
+ readonly entries: MongoNamespaceEntries = Object.freeze({
13
+ collection: Object.freeze({}),
14
+ });
15
15
  declare readonly kind: string;
16
16
 
17
17
  private constructor() {
@@ -9,7 +9,7 @@ function storageDeclaresCollection(
9
9
  collectionName: string,
10
10
  ): boolean {
11
11
  for (const ns of Object.values(storage.namespaces)) {
12
- if (Object.hasOwn(ns.entries.collection, collectionName)) {
12
+ if (Object.hasOwn(ns.entries['collection'] ?? {}, collectionName)) {
13
13
  return true;
14
14
  }
15
15
  }