@afora/core 0.0.4 → 0.0.6

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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/dist/core/application/exceptions.d.ts +9 -0
  3. package/dist/core/application/providers.d.ts +46 -0
  4. package/dist/core/application/types/application.interface.d.ts +70 -15
  5. package/dist/core/blueprint/types/slice.interface.d.ts +5 -2
  6. package/dist/core/entity/abstract-entity.d.ts +1 -1
  7. package/dist/core/entity/types/entity.interface.d.ts +13 -7
  8. package/dist/core/exception/exception.d.ts +10 -1
  9. package/dist/core/output/status.d.ts +2 -2
  10. package/dist/core/property/property.d.ts +4 -4
  11. package/dist/core/property/types/optional-property.type.d.ts +2 -2
  12. package/dist/core/property/types/property-type.interface.d.ts +17 -0
  13. package/dist/core/record/abstract-record.d.ts +4 -4
  14. package/dist/core/record/exceptions.d.ts +2 -0
  15. package/dist/core/record/name.d.ts +16 -0
  16. package/dist/core/record/record.d.ts +4 -3
  17. package/dist/core/record/types/plugin.interface.d.ts +11 -0
  18. package/dist/core/record/types/record.interface.d.ts +71 -10
  19. package/dist/core/repository/cursor.d.ts +13 -0
  20. package/dist/core/repository/exceptions.d.ts +10 -0
  21. package/dist/core/repository/query.d.ts +4 -4
  22. package/dist/core/repository/repository.d.ts +1 -1
  23. package/dist/core/repository/state.d.ts +3 -2
  24. package/dist/core/repository/types/query.interface.d.ts +37 -0
  25. package/dist/core/repository/types/repository.interface.d.ts +10 -6
  26. package/dist/core/use-case/steps/types/steps.interface.d.ts +1 -1
  27. package/dist/experimental.js +3 -2
  28. package/dist/{index-hn82hm31.js → index-3nd9bjnc.js} +69 -10
  29. package/dist/{index-ewm6r4nw.js → index-gbgc40xe.js} +452 -96
  30. package/dist/index.d.ts +9 -7
  31. package/dist/index.js +16 -2
  32. package/dist/std/properties/password.d.ts +5 -1
  33. package/dist/std/record-plugins.d.ts +5 -5
  34. package/dist/testing/index.js +91 -2
  35. package/dist/testing/provider-contract.d.ts +5 -0
  36. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
- import type { AnyBlueprint, BlueprintData, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, RecordAccepts } from "../../blueprint/types/blueprint.interface.js";
1
+ import type { AnyBlueprint, AnyRecordClass, BlueprintData, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, RecordAccepts } from "../../blueprint/types/blueprint.interface.js";
2
2
  import type { ParseResult } from "../../parse/types/parse.interface.js";
3
3
  import type { StandardSchemaV1 } from "../../standard/standard-schema.js";
4
4
  import type { Kind } from "../../symbols/kind.js";
5
5
  import type { Meta } from "../../symbols/meta.js";
6
6
  import type { AbstractRecord } from "../abstract-record.js";
7
- import type { AnyRecordPlugin, RecordPluginsOption, RecordUniqueKeys } from "./plugin.interface.js";
7
+ import type { AnyRecordPlugin, RecordPluginsOption, RecordSensitiveKeys, RecordUniqueKeys } from "./plugin.interface.js";
8
8
  /** The value of a record, read from its blueprint. */
9
9
  export type RecordValue<Record extends AnyRecordShape> = Record extends {
10
10
  readonly [Meta]: {
@@ -41,13 +41,21 @@ export type RecordChange<Record extends AnyRecordShape> = {
41
41
  /** A key of the blueprint of a record. */
42
42
  export type RecordKey<Properties extends IBlueprintCollection> = keyof Properties & string;
43
43
  /** What `recordOf` takes, after the blueprint, to describe its keys. */
44
- export interface RecordOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown> {
44
+ export interface RecordOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown, Sensitive extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[]> {
45
+ /**
46
+ * The name of the record, read with `recordNameOf(Class)` (or
47
+ * `Class.recordName`): an identifier that stays the same when a bundler
48
+ * renames the class. It names the record in exceptions, in the context a
49
+ * repository gives its provider (`context.name` is `<name>Repository`) and
50
+ * in OpenAPI. Without it, the name of the class is used.
51
+ */
52
+ readonly name?: string;
45
53
  /**
46
54
  * Keys whose values `toJSON` masks, every leaf as `MASK`, at any depth. A
47
55
  * key holding a property declared `sensitive` (as `Password`), or a list of
48
56
  * them, is always sensitive, and such a property is masked at any depth.
49
57
  */
50
- readonly sensitive?: readonly RecordKey<Properties>[];
58
+ readonly sensitive?: Sensitive;
51
59
  /**
52
60
  * Keys whose value no two records may share. Records only list them in
53
61
  * `options.unique`: repositories (or the database) enforce it. A key
@@ -91,45 +99,98 @@ interface RecordChangeNamespace<Properties extends IBlueprintCollection, Change
91
99
  type RecordChangeMethods<Self, Properties extends IBlueprintCollection, Key extends string> = {
92
100
  readonly [K in Key]: (value: K extends keyof BlueprintValue<Properties> ? BlueprintValue<Properties>[K] : never) => Self;
93
101
  };
94
- export interface IRecord<Properties extends IBlueprintCollection> {
102
+ /**
103
+ * A sensitive value as `toJSON` gives it: each leaf (string, number, boolean,
104
+ * bigint) is the string `MASK`; lists and objects keep their shape, and
105
+ * `null` and `undefined` stay.
106
+ */
107
+ export type Masked<Value> = Value extends null | undefined ? Value : Value extends readonly (infer Item)[] ? Masked<Item>[] : Value extends object ? {
108
+ readonly [Key in keyof Value]: Masked<Value[Key]>;
109
+ } : string;
110
+ /** What `toJSON` gives for `Field`, whose plain value is `Data`. */
111
+ type JsonField<Field, Data> = Field extends {
112
+ readonly type: {
113
+ readonly sensitive: true;
114
+ };
115
+ } ? Masked<Data> : Field extends {
116
+ readonly type: unknown;
117
+ } ? Data : Field extends AnyRecordClass ? JsonOfRecord<Field, Data> : Field extends {
118
+ readonly record: infer Class extends AnyRecordClass;
119
+ } ? JsonOfRecord<Class, Data> : Field extends {
120
+ readonly properties: infer Properties extends IBlueprintCollection;
121
+ } ? Data extends object ? JsonKeys<Properties, Data, never> : Data : Field extends {
122
+ readonly item: infer Item;
123
+ } ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Data;
124
+ /** What the `toJSON` of the records of `Class` gives, in place of their plain value. */
125
+ type JsonOfRecord<Class extends AnyRecordClass, Data> = InstanceType<Class> extends {
126
+ toJSON(): infer Json;
127
+ } ? Data extends object ? Json : Data : Data;
128
+ /**
129
+ * `Data`, the plain value of `Properties`, key by key as `toJSON` gives it.
130
+ * `Result` is a parameter so that no alias names the object.
131
+ */
132
+ type JsonKeys<Properties, Data, Sensitive, Result = {
133
+ readonly [Key in keyof Data]: Key extends Sensitive ? Masked<Data[Key]> : Key extends keyof Properties ? JsonField<Properties[Key], Data[Key]> : Data[Key];
134
+ }> = Result;
135
+ /**
136
+ * What `toJSON` of a record of `Properties` gives: every key of its plain
137
+ * value, with the keys of `Sensitive` and every property declared
138
+ * `sensitive`, at any depth, as `Masked`. `Result` is a parameter so that no
139
+ * alias names the object.
140
+ */
141
+ type JsonValue<Properties extends IBlueprintCollection, Sensitive, Result = 0 extends 1 & Properties ? Readonly<Record<string, unknown>> : string extends keyof Properties ? Readonly<Record<string, unknown>> : JsonKeys<Properties, BlueprintData<Properties>, Sensitive>> = Result;
142
+ /**
143
+ * The type of `toJSON()` of a record of `Properties` whose `options.sensitive`
144
+ * lists `Sensitive`: the plain value, no key left out, sensitive values masked.
145
+ */
146
+ export type RecordJson<Properties extends IBlueprintCollection, Sensitive extends string = never> = JsonValue<Properties, Sensitive>;
147
+ export interface IRecord<Properties extends IBlueprintCollection, Sensitive extends string = never> {
95
148
  /** Declaration metadata, separate from domain keys. */
96
149
  readonly [Meta]: {
97
150
  readonly blueprint: IBlueprint<Properties>;
98
151
  readonly options: RecordSettings<Properties>;
99
152
  };
100
153
  /** The value with the sensitive keys, and every `sensitive` property, masked. */
101
- toJSON(): Readonly<Partial<BlueprintData<Properties>>>;
154
+ toJSON(): RecordJson<Properties, Sensitive>;
102
155
  }
103
156
  /**
104
157
  * The records of the class `recordOf` returns, with what its plugins add,
105
158
  * `Members`.
106
159
  */
107
- export type DeclaredRecordInstance<Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown> = AbstractRecord<Properties> & Readonly<BlueprintValue<Properties>> & RecordChanges<Properties, Change> & Members;
160
+ export type DeclaredRecordInstance<Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown, Sensitive extends string = never> = AbstractRecord<Properties, Sensitive> & Readonly<BlueprintValue<Properties>> & RecordChanges<Properties, Change> & Members;
108
161
  /**
109
162
  * What `recordOf` returns: a class built from the value of the blueprint;
110
163
  * `Unique` are the keys of `options.unique`, and `Members` what its plugins
111
164
  * add to its records.
112
165
  */
113
- export type RecordConstructor<Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends string = never, Members = unknown, Accepts = never> = {
114
- new (value: BlueprintInput<Properties>): DeclaredRecordInstance<Properties, Change, Members>;
166
+ export type RecordConstructor<Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends string = never, Members = unknown, Accepts = never, Sensitive extends string = never> = {
167
+ new (value: BlueprintInput<Properties>): DeclaredRecordInstance<Properties, Change, Members, Sensitive>;
115
168
  /**
116
169
  * The Standard Schema of the class: `validate` gives an instance of it, or
117
170
  * the issues that reject the value. Typed with the instance of the class
118
171
  * `recordOf` returns, not of a subclass.
119
172
  */
120
- readonly "~standard": StandardSchemaV1.Props<BlueprintInput<Properties>, DeclaredRecordInstance<Properties, Change, Members>>;
173
+ readonly "~standard": StandardSchemaV1.Props<BlueprintInput<Properties>, DeclaredRecordInstance<Properties, Change, Members, Sensitive>>;
121
174
  /**
122
175
  * An instance of the class (a subclass included) for `value`, or every
123
176
  * issue that rejects it; never throws for an invalid value.
124
177
  */
125
178
  safeParse<Self extends new (value: never) => object>(this: Self, value: unknown): ParseResult<InstanceType<Self>>;
126
179
  readonly [Kind]: "record";
180
+ /**
181
+ * The name of the class as declared: the `name` option, which a bundler
182
+ * does not rename, or the name of the class without one. A subclass may
183
+ * give its own with `static override readonly recordName = "..."`.
184
+ */
185
+ readonly recordName: string;
127
186
  readonly blueprint: IBlueprint<Properties>;
128
187
  readonly options: RecordSettings<Properties>;
129
188
  /** Its plugins, those they require included, each once. */
130
189
  readonly plugins: readonly AnyRecordPlugin[];
131
190
  /** The keys of `options.unique`, for the type of a plugin of the class. */
132
191
  readonly [RecordUniqueKeys]?: Unique;
192
+ /** The keys of `options.sensitive`, for the type of a plugin of the class. */
193
+ readonly [RecordSensitiveKeys]?: Sensitive;
133
194
  /** More values a field of this class takes, as a sliced record takes the original's records. */
134
195
  readonly [RecordAccepts]?: Accepts;
135
196
  };
@@ -0,0 +1,13 @@
1
+ import type { RepositoryOrder } from "./types/query.interface.js";
2
+ import type { RepositoryData } from "./types/repository.interface.js";
3
+ /**
4
+ * The cursor of `row` in `orders`: an opaque text holding the values of its
5
+ * order keys. It is not encrypted or signed: whoever has it can read those
6
+ * values, and changing it only moves the position inside the same query.
7
+ */
8
+ export declare function encodeCursor(orders: readonly RepositoryOrder[], row: RepositoryData): string;
9
+ /**
10
+ * The values a cursor holds, one for each of `orders`; throws
11
+ * `InvalidRepositoryCursor` when it is not a cursor or was made for another order.
12
+ */
13
+ export declare function decodeCursor(source: string, orders: readonly RepositoryOrder[], cursor: unknown): readonly unknown[];
@@ -1,7 +1,17 @@
1
+ import type { Issue } from "../parse/types/parse.interface.js";
1
2
  /** A query contains unsupported keys, ordering or pagination. */
2
3
  export declare const InvalidRepositoryQuery: import("../../index.js").DeclaredExceptionClass<{
3
4
  readonly reason: string;
4
5
  }>;
6
+ /**
7
+ * `findCursor` got an `after` that is not a cursor it gave, or one made for
8
+ * another `orderBy`; `source` is the repository name. Its `issues` name the
9
+ * `after` of the query, so an output answers it as invalid input.
10
+ */
11
+ export declare const InvalidRepositoryCursor: import("../../index.js").DeclaredExceptionClass<{
12
+ readonly reason: string;
13
+ readonly issues: readonly Issue[];
14
+ }>;
5
15
  /** `repositoryOf` got something other than an entity, actions and a provider. */
6
16
  export declare const InvalidRepositoryDeclaration: import("../../index.js").DeclaredExceptionClass<{
7
17
  readonly reason: string;
@@ -1,9 +1,9 @@
1
- import type { RepositoryQuery } from "./types/query.interface.js";
1
+ import type { ProviderQuery, RepositoryQuery } from "./types/query.interface.js";
2
2
  import type { RepositoryContext, RepositoryData } from "./types/repository.interface.js";
3
3
  /** Validates query structure and optionally normalizes domain values for storage. */
4
- export declare function prepareQuery(context: RepositoryContext, input?: unknown, map?: (key: string, value: unknown) => unknown): RepositoryQuery;
4
+ export declare function prepareQuery(context: RepositoryContext, input?: unknown, map?: (key: string, value: unknown) => unknown, options?: readonly string[]): RepositoryQuery;
5
5
  /** Structural JSON/data equality without depending on object key insertion order. */
6
6
  export declare function sameValue(a: unknown, b: unknown): boolean;
7
7
  export declare function matchesQuery(data: RepositoryData, query: RepositoryQuery): boolean;
8
- /** The rows of `query` after ordering, offset and limit. */
9
- export declare function selectRows(rows: Iterable<RepositoryData>, query: RepositoryQuery): RepositoryData[];
8
+ /** The rows of `query` after ordering, `after`, offset and limit. */
9
+ export declare function selectRows(rows: Iterable<RepositoryData>, query: ProviderQuery): RepositoryData[];
@@ -52,7 +52,7 @@ export declare abstract class AbstractRepository<Entity extends AnyRepositoryEnt
52
52
  * unique key (`id`, `options.unique`, a `unique` property), giving the entity
53
53
  * or `undefined`; `findManyBy<Key>` for any other key, giving a list;
54
54
  * `create`, `update`, `delete`, `count`, and the queries `findMany`,
55
- * `exists` and `findPage`. Every method is async and reads and writes through `provider`
55
+ * `exists`, `findPage` and `findCursor`. Every method is async and reads and writes through `provider`
56
56
  * (a new `inMemoryProvider()` by default), which gets the variables of
57
57
  * `environmentDependencies` as `context.env`. The repository can be a
58
58
  * dependency of a use case, and `RepositoryOf` extends it with a class.
@@ -39,6 +39,7 @@ export interface RepositoryInspection {
39
39
  export declare function inspectRepository(repository: unknown): RepositoryInspection | undefined;
40
40
  /**
41
41
  * A repository sharing the declaration of `repository` that reads and writes
42
- * through `provider`, unless it declared a provider of its own.
42
+ * through `provider`, unless it declared a provider of its own; a declared
43
+ * provider found in `instances` is replaced by its value.
43
44
  */
44
- export declare function bindRepository<Repository extends object>(repository: Repository, provider: IRepositoryProvider): Repository;
45
+ export declare function bindRepository<Repository extends object>(repository: Repository, provider: IRepositoryProvider | undefined, instances?: ReadonlyMap<object, IRepositoryProvider>): Repository;
@@ -30,6 +30,41 @@ export interface RepositoryPage<Entity> {
30
30
  readonly limit: number;
31
31
  readonly offset: number;
32
32
  }
33
+ /**
34
+ * The query of `findCursor`: as `RepositoryQuery`, with the `next` of the
35
+ * previous page as `after` in place of `offset`. `where`, `orderBy` and
36
+ * `withDeleted` must stay the same from one page to the next.
37
+ */
38
+ export interface RepositoryCursorQuery<Entity = unknown> extends Omit<RepositoryQuery<Entity>, "offset" | "limit"> {
39
+ /** Positive integer; 20 when left out. */
40
+ readonly limit?: number;
41
+ /** The `next` of the page before; the first page when left out. */
42
+ readonly after?: string;
43
+ }
44
+ /** A page read by position: its items and, when there are more, the cursor of the next. */
45
+ export interface RepositoryCursorPage<Entity> {
46
+ readonly items: readonly EntityInstanceOf<Entity>[];
47
+ readonly limit: number;
48
+ /** What to give as `after` for the next page; `undefined` on the last one. */
49
+ readonly next: string | undefined;
50
+ }
51
+ /**
52
+ * What a provider gets from `findCursor` on `findMany`: `after` holds one
53
+ * value for each order of `orderBy`, those of the last row already read, and
54
+ * the provider gives the rows strictly after it in that order. Only a
55
+ * provider with `capabilities.keyset` is given it.
56
+ */
57
+ export interface ProviderQuery extends RepositoryQuery {
58
+ readonly after?: readonly unknown[];
59
+ }
60
+ export interface RepositoryFindCursor<Entity> {
61
+ /**
62
+ * A page of `limit` items (20 by default) after the cursor `after`. Unlike
63
+ * `findPage` it reads one page with one query, does not count, and stays
64
+ * correct when rows are added or removed between pages.
65
+ */
66
+ findCursor(query?: RepositoryCursorQuery<Entity>): Promise<RepositoryCursorPage<Entity>>;
67
+ }
33
68
  export interface RepositoryFindMany<Entity> {
34
69
  findMany(query?: RepositoryQuery<Entity>): Promise<EntityInstanceOf<Entity>[]>;
35
70
  }
@@ -57,6 +92,8 @@ export type ProviderDescription = {
57
92
  export interface ProviderCapabilities {
58
93
  readonly operators?: readonly string[];
59
94
  readonly transactions?: boolean;
95
+ /** Whether `findMany` honors `after` (`ProviderQuery`), which `findCursor` needs. */
96
+ readonly keyset?: boolean;
60
97
  }
61
98
  /** Declaration input for a named provider, implemented by providerOf in T10. */
62
99
  export interface ProviderSettings<Options extends ProviderOptions = ProviderOptions, Provider extends IRepositoryProvider = IRepositoryProvider> {
@@ -3,7 +3,7 @@ import type { AbstractEntity, EntityUniqueKeys } from "../../entity/abstract-ent
3
3
  import type { AnyEntityClass } from "../../entity/event.js";
4
4
  import type { RecordSettings } from "../../record/types/record.interface.js";
5
5
  import type { AbstractRepository } from "../repository.js";
6
- import type { ProviderCapabilities, RepositoryExists, RepositoryFindMany, RepositoryFindPage, RepositoryQuery } from "./query.interface.js";
6
+ import type { ProviderCapabilities, ProviderQuery, RepositoryExists, RepositoryFindCursor, RepositoryFindMany, RepositoryFindPage, RepositoryQuery } from "./query.interface.js";
7
7
  /** Any class made by `entityOf`, or extending one, that a repository holds. */
8
8
  export type AnyRepositoryEntity = AnyRecordClass & AnyEntityClass & {
9
9
  readonly [EntityUniqueKeys]: readonly string[];
@@ -67,9 +67,9 @@ export type RepositoryFindManyBy<Entity, Actions extends string = string> = {
67
67
  * so that a class extending a repository can override them.
68
68
  * @internal
69
69
  */
70
- export type RepositoryMethods<Entity, Actions extends string = string> = RepositoryFindBy<Entity, Actions> & RepositoryFindManyBy<Entity, Actions> & ("create" extends Actions ? RepositoryCreate<Entity> : unknown) & ("update" extends Actions ? RepositoryUpdate<Entity> : unknown) & ("delete" extends Actions ? RepositoryDelete<Entity> : unknown) & ("count" extends Actions ? RepositoryCount<Entity> : unknown) & ("findMany" extends Actions ? RepositoryFindMany<Entity> : unknown) & ("exists" extends Actions ? RepositoryExists<Entity> : unknown) & ("findPage" extends Actions ? RepositoryFindPage<Entity> : unknown);
70
+ export type RepositoryMethods<Entity, Actions extends string = string> = RepositoryFindBy<Entity, Actions> & RepositoryFindManyBy<Entity, Actions> & ("create" extends Actions ? RepositoryCreate<Entity> : unknown) & ("update" extends Actions ? RepositoryUpdate<Entity> : unknown) & ("delete" extends Actions ? RepositoryDelete<Entity> : unknown) & ("count" extends Actions ? RepositoryCount<Entity> : unknown) & ("findMany" extends Actions ? RepositoryFindMany<Entity> : unknown) & ("exists" extends Actions ? RepositoryExists<Entity> : unknown) & ("findPage" extends Actions ? RepositoryFindPage<Entity> : unknown) & ("findCursor" extends Actions ? RepositoryFindCursor<Entity> : unknown);
71
71
  /** A method a repository of `Entity` can have, e.g. `"findByEmail"`. */
72
- export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "count" | "findMany" | "exists" | "findPage";
72
+ export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "count" | "findMany" | "exists" | "findPage" | "findCursor";
73
73
  /**
74
74
  * A repository of `Entity` with the methods of `Actions`, whose provider gets
75
75
  * `Environment` as `context.env`, e.g.
@@ -82,9 +82,12 @@ export type AnyRepository = AbstractRepository<any, any, any>;
82
82
  export type RepositoryData = Readonly<Record<string, unknown>>;
83
83
  /** What a provider gets with every call: whose entities, and the environment. */
84
84
  export interface RepositoryContext<Environment = unknown> {
85
- /** The entity class, e.g. to name a table after it. */
85
+ /** The entity class; name a table after `recordNameOf(entity)`, not `entity.name`. */
86
86
  readonly entity: AnyRepositoryEntity;
87
- /** The name of the repository, for the source of its exceptions. */
87
+ /**
88
+ * The name of the repository, `<entity name>Repository`, for the source of
89
+ * its exceptions; stable under minification when the entity declares a `name`.
90
+ */
88
91
  readonly name: string;
89
92
  /** The keys no two entities may share, `id` first. */
90
93
  readonly unique: readonly string[];
@@ -99,7 +102,8 @@ export interface RepositoryContext<Environment = unknown> {
99
102
  */
100
103
  export interface IRepositoryProvider<Environment = unknown> {
101
104
  findOne(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<RepositoryData | undefined>;
102
- findMany(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<readonly RepositoryData[]>;
105
+ /** `query.after` is only given to a provider with `capabilities.keyset`. */
106
+ findMany(context: RepositoryContext<Environment>, query?: ProviderQuery): Promise<readonly RepositoryData[]>;
103
107
  insert(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
104
108
  update(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
105
109
  remove(context: RepositoryContext<Environment>, id: string): Promise<void>;
@@ -11,7 +11,7 @@ export type StepValue = StepReference | string | number | boolean | null | reado
11
11
  readonly [key: string]: StepValue;
12
12
  };
13
13
  /** The actions of a repository that only read. */
14
- export type StepReadAction = `findBy${string}` | `findManyBy${string}` | "findMany" | "exists" | "findPage" | "count";
14
+ export type StepReadAction = `findBy${string}` | `findManyBy${string}` | "findMany" | "exists" | "findPage" | "findCursor" | "count";
15
15
  type RepositoryKeys<Dependencies> = {
16
16
  [Key in keyof Dependencies & string]: Dependencies[Key] extends AnyRepository ? Key : never;
17
17
  }[keyof Dependencies & string];
@@ -2,6 +2,7 @@ import {
2
2
  isBlueprint2,
3
3
  isRecordClass2,
4
4
  recordFieldOf2,
5
+ recordNameOf2,
5
6
  listOf2,
6
7
  isBlueprintList2,
7
8
  AbstractRecord2,
@@ -9,7 +10,7 @@ import {
9
10
  UndeclaredBlueprintProperty2,
10
11
  optionalOf2,
11
12
  blueprintFromProperties
12
- } from "./index-hn82hm31.js";
13
+ } from "./index-3nd9bjnc.js";
13
14
 
14
15
  // src/core/blueprint/slice.ts
15
16
  function propertiesOfField(field) {
@@ -69,7 +70,7 @@ function slicedRecordOf(Original, mask, path) {
69
70
  sensitive: kept(sensitive),
70
71
  unique: kept(unique),
71
72
  change: kept(change)
72
- }, Original.name);
73
+ }, recordNameOf2(Original));
73
74
  Object.defineProperty(Sliced, Symbol.hasInstance, {
74
75
  value(value) {
75
76
  return Function.prototype[Symbol.hasInstance].call(this, value) || value instanceof Original;
@@ -17,6 +17,7 @@ var __export = (target, all) => {
17
17
  var exports_exceptions = {};
18
18
  __export(exports_exceptions, {
19
19
  IncompatibleRecordPlugin: () => IncompatibleRecordPlugin2,
20
+ InvalidRecordDeclaration: () => InvalidRecordDeclaration2,
20
21
  InvalidRecordPlugin: () => InvalidRecordPlugin2,
21
22
  InvalidRecordValue: () => InvalidRecordValue2,
22
23
  RecordPluginConflict: () => RecordPluginConflict2,
@@ -307,6 +308,31 @@ function recordFieldOf2(field) {
307
308
  return;
308
309
  }
309
310
 
311
+ // src/core/record/name.ts
312
+ var RECORD_NAME = /^[A-Za-z_$][\w$]*$/;
313
+ function isRecordName(name) {
314
+ return typeof name === "string" && RECORD_NAME.test(name);
315
+ }
316
+ function recordNameOf2(Class) {
317
+ const { recordName, name } = Class;
318
+ return typeof recordName === "string" ? recordName : String(name);
319
+ }
320
+ var declaredNames = new WeakMap;
321
+ function declareRecordName(Class, name) {
322
+ declaredNames.set(Class, name);
323
+ }
324
+ function declaredRecordNameOf(Class) {
325
+ for (let current = Class;typeof current === "function"; current = Object.getPrototypeOf(current)) {
326
+ const own = Object.getOwnPropertyDescriptor(current, "recordName")?.value;
327
+ if (typeof own === "string")
328
+ return own;
329
+ const declared = declaredNames.get(current);
330
+ if (declared !== undefined)
331
+ return declared;
332
+ }
333
+ return;
334
+ }
335
+
310
336
  // src/core/parse/issue.ts
311
337
  function withLazyCause(issue, build) {
312
338
  let cause;
@@ -454,7 +480,7 @@ function fieldAt(field, value, parent, key, source) {
454
480
  if (value instanceof record.record)
455
481
  return { value };
456
482
  if (!isPlainObject(value)) {
457
- return rejection("not_record", `must be an instance of record "${record.record.name}"`, pathAt(parent, key), source);
483
+ return rejection("not_record", `must be an instance of record "${recordNameOf2(record.record)}"`, pathAt(parent, key), source);
458
484
  }
459
485
  const result = record.record.safeParse(value);
460
486
  return result.issues ? underPath(result, pathAt(parent, key)) : result;
@@ -587,14 +613,16 @@ class Exception2 extends Error {
587
613
  message = this.message,
588
614
  details
589
615
  }) {
590
- return new Exception2({
616
+ const keepsClass = name === this.name && details === undefined && isDeclaredInstance(this);
617
+ const init = {
591
618
  layer,
592
619
  source,
593
620
  name,
594
621
  message,
595
- details,
622
+ details: keepsClass ? this.details : details,
596
623
  cause: this
597
- });
624
+ };
625
+ return keepsClass ? Reflect.construct(Exception2, [init], this.constructor) : new Exception2(init);
598
626
  }
599
627
  toJSON() {
600
628
  return { ...this.frame, trace: this.trace };
@@ -612,6 +640,13 @@ class Exception2 extends Error {
612
640
  }
613
641
  }
614
642
  var declarations = new WeakMap;
643
+ function isDeclaredInstance(exception) {
644
+ for (let current = exception.constructor;typeof current === "function"; current = Object.getPrototypeOf(current)) {
645
+ if (declarations.has(current))
646
+ return true;
647
+ }
648
+ return false;
649
+ }
615
650
  function isDeclaredException(value) {
616
651
  return typeof value === "function" && declarations.has(value);
617
652
  }
@@ -660,6 +695,11 @@ var InvalidRecordValue2 = exceptionOf2({
660
695
  errors
661
696
  }, source) => `Invalid value for ${key ? `"${key}" of ` : ""}record "${source}": ${errors.join("; ")}`
662
697
  });
698
+ var InvalidRecordDeclaration2 = exceptionOf2({
699
+ layer: "record" /* Record */,
700
+ name: "InvalidRecordDeclaration",
701
+ message: (reason, source) => `Invalid declaration of record "${source}": ${reason}`
702
+ });
663
703
  var UndeclaredRecordProperty2 = exceptionOf2({
664
704
  layer: "record" /* Record */,
665
705
  name: "UndeclaredRecordProperty",
@@ -781,7 +821,10 @@ function checkMembers(record, keys) {
781
821
  for (let prototype = Object.getPrototypeOf(record);prototype !== null && !Object.hasOwn(prototype.constructor, "blueprint"); prototype = Object.getPrototypeOf(prototype)) {
782
822
  const key = Object.getOwnPropertyNames(prototype).find((name) => keys.includes(name));
783
823
  if (key !== undefined) {
784
- throw new ReservedRecordKey2({ source: Class.name, details: { key } });
824
+ throw new ReservedRecordKey2({
825
+ source: recordNameOf2(Class),
826
+ details: { key }
827
+ });
785
828
  }
786
829
  }
787
830
  checked.add(Class);
@@ -837,7 +880,7 @@ function parseRecord(Class, value) {
837
880
  try {
838
881
  return { value: new Class(value) };
839
882
  } catch (error) {
840
- return { issues: issuesOfError(error, Class.name) };
883
+ return { issues: issuesOfError(error, recordNameOf2(Class)) };
841
884
  }
842
885
  }
843
886
  function recordValue(record) {
@@ -868,7 +911,7 @@ function recordFieldDataOf(field, value, options) {
868
911
  return value;
869
912
  }
870
913
  function storeRecordValue(record, key, value) {
871
- const source = record.constructor.name;
914
+ const source = recordNameOf2(record.constructor);
872
915
  const { properties } = record[Meta2].blueprint;
873
916
  if (!Object.hasOwn(properties, key)) {
874
917
  throw exceptionOfIssues([
@@ -921,7 +964,7 @@ class AbstractRecord2 {
921
964
  }
922
965
  constructor(value) {
923
966
  checkMembers(this, Object.keys(this[Meta2].blueprint.properties));
924
- const source = this.constructor.name;
967
+ const source = recordNameOf2(this.constructor);
925
968
  const result = parseCollection(this[Meta2].blueprint.properties, value, [], source);
926
969
  if (result.issues)
927
970
  throw exceptionOfIssues(result.issues, source);
@@ -1140,7 +1183,14 @@ function pluginContextOf(catalogs) {
1140
1183
  return Object.freeze(Object.assign({}, ...catalogs.map((catalog) => catalog.plugins)));
1141
1184
  }
1142
1185
  var recordOf3 = recordBuilderOf2();
1143
- function declareRecord(Base, declared, { plugins = [], ...declaredOptions }, name, reserved = [], context = {}) {
1186
+ function declareRecord(Base, declared, { plugins = [], name: declaredName, ...declaredOptions }, fallback, reserved = [], context = {}) {
1187
+ if (declaredName !== undefined && !isRecordName(declaredName)) {
1188
+ throw new InvalidRecordDeclaration2({
1189
+ source: fallback,
1190
+ details: `"name" must be an identifier, got ${JSON.stringify(declaredName)}.`
1191
+ });
1192
+ }
1193
+ const name = declaredName ?? fallback;
1144
1194
  const applied = applyPlugins(declared, typeof plugins === "function" ? plugins(context) : plugins, name, reserved);
1145
1195
  const blueprint = applied.blueprint;
1146
1196
  const sensitive = [
@@ -1192,6 +1242,15 @@ function declareRecord(Base, declared, { plugins = [], ...declaredOptions }, nam
1192
1242
  static [RecordPlugins] = applied.plugins;
1193
1243
  static [RecordPluginHooks] = applied.hooks;
1194
1244
  }
1245
+ if (declaredName !== undefined) {
1246
+ declareRecordName(DeclaredRecord, declaredName);
1247
+ }
1248
+ Object.defineProperty(DeclaredRecord, "recordName", {
1249
+ get() {
1250
+ return declaredName ?? this.name;
1251
+ },
1252
+ configurable: true
1253
+ });
1195
1254
  exposeRecordKeys(DeclaredRecord.prototype, keys);
1196
1255
  for (const [member, descriptor] of Object.entries(applied.descriptors)) {
1197
1256
  Object.defineProperty(DeclaredRecord.prototype, member, descriptor);
@@ -1648,4 +1707,4 @@ function schemaOfField(key, field) {
1648
1707
  });
1649
1708
  }
1650
1709
 
1651
- export { __export, isProperty2, schemaOf2, Demo2, recordPropertyLineage, propertyLineageOf2, fieldOf, Kind2, isBlueprint2, isRecordClass2, isOptionalRecord2, recordFieldOf2, listOf2, isBlueprintList2, ExceptionLayers2, UNEXPECTED_ERROR2, Exception2, isDeclaredException, declaredMessageOf, exceptionOf2, Meta2, InvalidRecordValue2, UndeclaredRecordProperty2, ReservedRecordKey2, InvalidRecordPlugin2, RecordPluginConflict2, UnknownRecordPlugin2, IncompatibleRecordPlugin2, exports_exceptions, MASK2, RESERVED_RECORD_KEYS2, recordDataOf, recordFieldDataOf, storeRecordValue, runRecordEffects, AbstractRecord2, IdentifiedRecord, isVersion, versionParts, pluginsOf2, isRecordPlugin2, hasPlugin2, recordPluginOf2, recordBuilderOf2, pluginContextOf, recordOf3, declareRecord, ReservedBlueprintKey2, UndeclaredBlueprintProperty2, exports_exceptions2, PropertyRequired2, InvalidPropertyValue2, exports_exceptions3, State2, AbstractProperty2, PropertyStates2, AbstractDefineProperty2, propertyOf2, optionalOf2, blueprintBuilderOf2, blueprintFromProperties };
1710
+ export { __export, isProperty2, schemaOf2, Demo2, recordPropertyLineage, propertyLineageOf2, fieldOf, Kind2, isBlueprint2, isRecordClass2, isOptionalRecord2, recordFieldOf2, recordNameOf2, declaredRecordNameOf, listOf2, isBlueprintList2, ExceptionLayers2, UNEXPECTED_ERROR2, Exception2, isDeclaredException, declaredMessageOf, exceptionOf2, Meta2, InvalidRecordValue2, InvalidRecordDeclaration2, UndeclaredRecordProperty2, ReservedRecordKey2, InvalidRecordPlugin2, RecordPluginConflict2, UnknownRecordPlugin2, IncompatibleRecordPlugin2, exports_exceptions, MASK2, RESERVED_RECORD_KEYS2, recordDataOf, recordFieldDataOf, storeRecordValue, runRecordEffects, AbstractRecord2, IdentifiedRecord, isVersion, versionParts, pluginsOf2, isRecordPlugin2, hasPlugin2, recordPluginOf2, recordBuilderOf2, pluginContextOf, recordOf3, declareRecord, ReservedBlueprintKey2, UndeclaredBlueprintProperty2, exports_exceptions2, PropertyRequired2, InvalidPropertyValue2, exports_exceptions3, State2, AbstractProperty2, PropertyStates2, AbstractDefineProperty2, propertyOf2, optionalOf2, blueprintBuilderOf2, blueprintFromProperties };