@snowtop/ent 0.1.0-alpha123 → 0.1.0-alpha124

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/core/base.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as clause from "./clause";
2
+ import { ObjectLoaderFactory } from "./loaders";
2
3
  export interface Loader<K, V> {
3
4
  context?: Context;
4
5
  load(key: K): Promise<V>;
@@ -95,21 +96,21 @@ export interface EditRowOptions extends CreateRowOptions {
95
96
  whereClause: clause.Clause;
96
97
  expressions?: Map<string, clause.Clause>;
97
98
  }
98
- interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> {
99
- loaderFactory: LoaderFactoryWithOptions;
99
+ interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer, TData extends Data = Data> {
100
+ loaderFactory: ObjectLoaderFactory<TData>;
100
101
  ent: EntConstructor<TEnt, TViewer>;
101
102
  }
102
- export interface LoaderFactoryWithOptions extends LoaderFactoryWithLoaderMany<any, Data | null> {
103
+ export interface LoaderFactoryWithOptions<T extends Data = Data> extends LoaderFactoryWithLoaderMany<any, T | null> {
103
104
  options?: SelectDataOptions;
104
105
  }
105
- export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends LoadableEntOptions<TEnt, TViewer>, SelectBaseDataOptions {
106
+ export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer, TData extends Data = Data> extends LoadableEntOptions<TEnt, TViewer, TData>, SelectBaseDataOptions {
106
107
  fieldPrivacy?: Map<string, PrivacyPolicy>;
107
108
  }
108
- export interface SelectCustomDataOptions extends SelectBaseDataOptions {
109
- loaderFactory: LoaderFactoryWithOptions;
109
+ export interface SelectCustomDataOptions<T extends Data = Data> extends SelectBaseDataOptions {
110
+ loaderFactory: ObjectLoaderFactory<T>;
110
111
  prime?: boolean;
111
112
  }
112
- export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends SelectCustomDataOptions {
113
+ export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer, TData extends Data = Data> extends SelectCustomDataOptions<TData> {
113
114
  ent: EntConstructor<TEnt, TViewer>;
114
115
  fieldPrivacy?: Map<string, PrivacyPolicy>;
115
116
  }
package/core/clause.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Data } from "./base";
1
+ import { Data, SelectDataOptions } from "./base";
2
2
  export interface Clause<T extends Data = Data, K = keyof T> {
3
3
  clause(idx: number): string;
4
4
  columns(): K[];
@@ -102,4 +102,5 @@ export declare function Subtract<T extends Data, K = keyof T>(col: K, value: any
102
102
  export declare function Multiply<T extends Data, K = keyof T>(col: K, value: any): Clause<T, K>;
103
103
  export declare function Divide<T extends Data, K = keyof T>(col: K, value: any): Clause<T, K>;
104
104
  export declare function Modulo<T extends Data, K = keyof T>(col: K, value: any): Clause<T, K>;
105
+ export declare function getCombinedClause<V extends Data = Data, K = keyof V>(options: Omit<SelectDataOptions, "key">, cls: Clause<V, K>): Clause<V, K>;
105
106
  export {};
package/core/clause.js CHANGED
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Modulo = exports.Divide = exports.Multiply = exports.Subtract = exports.Add = exports.PaginationMultipleColsSubQuery = exports.JSONPathValuePredicate = exports.JSONObjectFieldKeyAsText = exports.JSONObjectFieldKeyASJSON = exports.sensitiveValue = exports.TsVectorWebsearchToTsQuery = exports.TsVectorPhraseToTsQuery = exports.TsVectorPlainToTsQuery = exports.TsVectorColTsQuery = exports.WebsearchToTsQuery = exports.PhraseToTsQuery = exports.PlainToTsQuery = exports.TsQuery = exports.In = exports.OrOptional = exports.Or = exports.AndOptional = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = exports.ArrayNotEq = exports.ArrayEq = exports.PostgresArrayNotOverlaps = exports.PostgresArrayOverlaps = exports.PostgresArrayNotContains = exports.PostgresArrayNotContainsValue = exports.PostgresArrayContains = exports.PostgresArrayContainsValue = exports.inClause = void 0;
26
+ exports.getCombinedClause = exports.Modulo = exports.Divide = exports.Multiply = exports.Subtract = exports.Add = exports.PaginationMultipleColsSubQuery = exports.JSONPathValuePredicate = exports.JSONObjectFieldKeyAsText = exports.JSONObjectFieldKeyASJSON = exports.sensitiveValue = exports.TsVectorWebsearchToTsQuery = exports.TsVectorPhraseToTsQuery = exports.TsVectorPlainToTsQuery = exports.TsVectorColTsQuery = exports.WebsearchToTsQuery = exports.PhraseToTsQuery = exports.PlainToTsQuery = exports.TsQuery = exports.In = exports.OrOptional = exports.Or = exports.AndOptional = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = exports.ArrayNotEq = exports.ArrayEq = exports.PostgresArrayNotOverlaps = exports.PostgresArrayOverlaps = exports.PostgresArrayNotContains = exports.PostgresArrayNotContainsValue = exports.PostgresArrayContains = exports.PostgresArrayContainsValue = exports.inClause = void 0;
27
27
  const db_1 = __importStar(require("./db"));
28
28
  function isSensitive(val) {
29
29
  return (val !== null &&
@@ -731,3 +731,20 @@ function Modulo(col, value) {
731
731
  return new simpleClause(col, value, "%", new isNullClause(col));
732
732
  }
733
733
  exports.Modulo = Modulo;
734
+ function getCombinedClause(options, cls) {
735
+ if (options.clause) {
736
+ let optionClause;
737
+ if (typeof options.clause === "function") {
738
+ optionClause = options.clause();
739
+ }
740
+ else {
741
+ optionClause = options.clause;
742
+ }
743
+ if (optionClause) {
744
+ // @ts-expect-error different types
745
+ cls = And(cls, optionClause);
746
+ }
747
+ }
748
+ return cls;
749
+ }
750
+ exports.getCombinedClause = getCombinedClause;
package/core/ent.d.ts CHANGED
@@ -23,13 +23,13 @@ export declare function loadEntsList<TEnt extends Ent<TViewer>, TViewer extends
23
23
  * @deperecated use loadCustomEnts
24
24
  */
25
25
  export declare function loadEntsFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, clause: clause.Clause, options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
26
- export declare function loadCustomEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer, TQueryData extends Data = Data, TResultData extends Data = TQueryData, TKey = keyof TQueryData>(viewer: TViewer, options: LoadCustomEntOptions<TEnt, TViewer>, query: CustomQuery<TQueryData, TKey>): Promise<TEnt[]>;
26
+ export declare function loadCustomEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer, TQueryData extends Data = Data, TResultData extends Data = TQueryData, TKey = keyof TQueryData>(viewer: TViewer, options: LoadCustomEntOptions<TEnt, TViewer, TResultData>, query: CustomQuery<TQueryData, TKey>): Promise<TEnt[]>;
27
27
  interface parameterizedQueryOptions {
28
28
  query: string;
29
29
  values?: any[];
30
30
  logValues?: any[];
31
31
  }
32
- export type CustomQuery<T extends Data = Data, K = keyof T> = string | parameterizedQueryOptions | clause.Clause<T, K> | QueryDataOptions;
32
+ export type CustomQuery<T extends Data = Data, K = keyof T> = string | parameterizedQueryOptions | clause.Clause<T, K> | QueryDataOptions<T, K>;
33
33
  /**
34
34
  * Note that if there's default read transformations (e.g. soft delete) and a clause is passed in
35
35
  * either as Clause or QueryDataOptions without {disableTransformations: true}, the default transformation
@@ -53,11 +53,15 @@ export type CustomQuery<T extends Data = Data, K = keyof T> = string | parameter
53
53
  * orderby: 'time',
54
54
  * disableTransformations: false
55
55
  * }) // doesn't change the query
56
+ *
57
+ * For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
58
+ * or parallel queries with the same clause are batched together.
59
+ *
60
+ * If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
61
+ * If you end up with a scenario where you may need to coalesce or batch (non-clause) queries here, you should use some kind of memoization here.
56
62
  */
57
- export declare function loadCustomData<TQueryData extends Data = Data, TResultData extends Data = TQueryData, K = keyof TQueryData>(options: SelectCustomDataOptions, query: CustomQuery<TQueryData, K>, context: Context | undefined): Promise<TResultData[]>;
58
- interface CustomCountOptions extends DataOptions {
59
- }
60
- export declare function loadCustomCount<T extends Data = Data, K = keyof T>(options: CustomCountOptions, query: CustomQuery<T, K>, context: Context | undefined): Promise<number>;
63
+ export declare function loadCustomData<TQueryData extends Data = Data, TResultData extends Data = TQueryData, K = keyof TQueryData>(options: SelectCustomDataOptions<TResultData>, query: CustomQuery<TQueryData, K>, context: Context | undefined): Promise<TResultData[]>;
64
+ export declare function loadCustomCount<T extends Data = Data, K = keyof T>(options: SelectCustomDataOptions<T>, query: CustomQuery<T, K>, context: Context | undefined): Promise<number>;
61
65
  export declare function loadDerivedEnt<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, data: Data, loader: new (viewer: TViewer, data: Data) => TEnt): Promise<TEnt | null>;
62
66
  export declare function loadDerivedEntX<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, data: Data, loader: new (viewer: TViewer, data: Data) => TEnt): Promise<TEnt>;
63
67
  export declare function logQuery(query: string, logValues: any[]): void;
package/core/ent.js CHANGED
@@ -397,6 +397,12 @@ function isParameterizedQuery(opts) {
397
397
  * orderby: 'time',
398
398
  * disableTransformations: false
399
399
  * }) // doesn't change the query
400
+ *
401
+ * For queries that pass in a clause, we batch them with an underlying dataloader so that multiple queries with the same clause
402
+ * or parallel queries with the same clause are batched together.
403
+ *
404
+ * If a raw or parameterized query is passed in, we don't attempt to batch them together and they're executed as is.
405
+ * If you end up with a scenario where you may need to coalesce or batch (non-clause) queries here, you should use some kind of memoization here.
400
406
  */
401
407
  async function loadCustomData(options, query, context) {
402
408
  const rows = await loadCustomDataImpl(options, query, context);
@@ -415,7 +421,10 @@ exports.loadCustomData = loadCustomData;
415
421
  // NOTE: if you use a raw query or paramterized query with this,
416
422
  // you should use `SELECT count(*) as count...`
417
423
  async function loadCustomCount(options, query, context) {
418
- // TODO also need to loaderify this in case we're querying for this a lot...
424
+ // if clause, we'll use the loader and strong typing/coalescing it provides
425
+ if (typeof query !== "string" && isClause(query)) {
426
+ return options.loaderFactory.createCountLoader(context).load(query);
427
+ }
419
428
  const rows = await loadCustomDataImpl({
420
429
  ...options,
421
430
  fields: ["count(1) as count"],
@@ -430,50 +439,31 @@ function isPrimableLoader(loader) {
430
439
  return loader != undefined;
431
440
  }
432
441
  async function loadCustomDataImpl(options, query, context) {
433
- function getClause(cls) {
434
- let optClause = options.loaderFactory?.options?.clause;
435
- if (typeof optClause === "function") {
436
- optClause = optClause();
437
- }
438
- if (!optClause) {
439
- return cls;
440
- }
441
- // @ts-expect-error string|ID mismatch
442
- return clause.And(cls, optClause);
443
- }
444
442
  if (typeof query === "string") {
445
443
  // no caching, perform raw query
446
444
  return performRawQuery(query, [], []);
447
- // @ts-ignore
448
445
  }
449
446
  else if (isClause(query)) {
450
- // if a Clause is passed in and we have a default clause
451
- // associated with the query, pass that in
452
- // if we want to disableTransformations, need to indicate that with
453
- // disableTransformations option
454
- // this will have rudimentary caching but nothing crazy
455
- return loadRows({
456
- ...options,
457
- // @ts-ignore
458
- clause: getClause(query),
459
- context: context,
460
- });
447
+ const r = await options.loaderFactory
448
+ .createTypedLoader(context)
449
+ .load(query);
450
+ return r;
461
451
  }
462
452
  else if (isParameterizedQuery(query)) {
463
453
  // no caching, perform raw query
464
454
  return performRawQuery(query.query, query.values || [], query.logValues);
465
455
  }
466
456
  else {
457
+ // this will have rudimentary caching but nothing crazy
467
458
  let cls = query.clause;
468
459
  if (!query.disableTransformations) {
469
- // @ts-ignore
470
- cls = getClause(cls);
460
+ cls = clause.getCombinedClause(options.loaderFactory.options, query.clause);
471
461
  }
472
- // this will have rudimentary caching but nothing crazy
473
462
  return loadRows({
474
463
  ...query,
475
464
  ...options,
476
465
  context: context,
466
+ // @ts-expect-error
477
467
  clause: cls,
478
468
  });
479
469
  }
@@ -21,7 +21,7 @@ export declare class AssocDirectEdgeLoader<T extends AssocEdge> implements Loade
21
21
  private edgeCtr;
22
22
  private options?;
23
23
  context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
24
- constructor(edgeType: string, edgeCtr: AssocEdgeConstructor<T>, options?: Partial<Pick<import("../base").QueryableDataOptions, "limit" | "orderby" | "clause">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
24
+ constructor(edgeType: string, edgeCtr: AssocEdgeConstructor<T>, options?: Partial<Pick<import("../base").QueryableDataOptions, "clause" | "limit" | "orderby">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
25
25
  load(id: ID): Promise<T[]>;
26
26
  loadEdgeForID2(id: ID, id2: ID): Promise<T | undefined>;
27
27
  clearAll(): void;
@@ -1,29 +1,49 @@
1
1
  import { ID, Data, SelectDataOptions, Context, Loader, LoaderFactory } from "../base";
2
- export declare class ObjectLoader<V = Data> implements Loader<ID, V | null> {
2
+ import * as clause from "../clause";
3
+ export declare class ObjectLoader<TQueryData extends Data = Data, TResultData extends Data = TQueryData, K = keyof TQueryData> implements Loader<ID, TResultData | null>, Loader<clause.Clause<TQueryData, K>, TResultData[] | null> {
3
4
  private options;
4
5
  context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
5
6
  private toPrime?;
6
- private loader;
7
+ private idLoader;
8
+ private clauseLoader;
7
9
  private primedLoaders;
8
10
  private memoizedInitPrime;
9
- constructor(options: SelectDataOptions, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined, toPrime?: ObjectLoaderFactory<V>[] | undefined);
11
+ constructor(options: SelectDataOptions, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined, toPrime?: ObjectLoaderFactory<TResultData>[] | undefined);
10
12
  getOptions(): SelectDataOptions;
11
13
  private initPrime;
12
- load(key: ID): Promise<V | null>;
14
+ load(key: ID): Promise<TResultData | null>;
15
+ load(key: clause.Clause<TQueryData, K>): Promise<TResultData[] | null>;
16
+ private loadID;
17
+ private loadClause;
18
+ clearAll(): void;
19
+ loadMany(keys: ID[]): Promise<Array<TResultData | null>>;
20
+ loadMany(keys: clause.Clause<TQueryData, K>[]): Promise<Array<TResultData[] | null>>;
21
+ private loadIDMany;
22
+ private loadClauseMany;
23
+ prime(data: TResultData): void;
24
+ primeAll(data: TResultData): void;
25
+ }
26
+ export declare class ObjectCountLoader<V extends Data = Data, K = keyof V> implements Loader<clause.Clause<V, K>, number> {
27
+ private options;
28
+ context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
29
+ private loader;
30
+ constructor(options: SelectDataOptions, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
31
+ getOptions(): SelectDataOptions;
32
+ load(key: clause.Clause<V, K>): Promise<number>;
13
33
  clearAll(): void;
14
- loadMany(keys: ID[]): Promise<Array<V | null>>;
15
- prime(data: V): void;
16
- primeAll(data: V): void;
34
+ loadMany(keys: clause.Clause<V, K>[]): Promise<Array<number>>;
17
35
  }
18
36
  interface ObjectLoaderOptions extends SelectDataOptions {
19
37
  instanceKey?: string;
20
38
  }
21
- export declare class ObjectLoaderFactory<V = Data> implements LoaderFactory<ID, V | null> {
39
+ export declare class ObjectLoaderFactory<V extends Data = Data> implements LoaderFactory<ID, V | null>, LoaderFactory<clause.Clause<V>, V[] | null> {
22
40
  options: ObjectLoaderOptions;
23
41
  name: string;
24
42
  private toPrime;
25
43
  constructor(options: ObjectLoaderOptions);
26
44
  createLoader(context?: Context): ObjectLoader<V>;
45
+ createTypedLoader<TQueryData extends Data = Data, TResultData extends Data = Data, K = keyof TQueryData>(context?: Context): ObjectLoader<TQueryData, TResultData, K>;
46
+ createCountLoader<K = keyof V>(context?: Context): ObjectCountLoader<V, K>;
27
47
  addToPrime(factory: ObjectLoaderFactory<V>): this;
28
48
  }
29
49
  export {};
@@ -26,29 +26,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.ObjectLoaderFactory = exports.ObjectLoader = void 0;
29
+ exports.ObjectLoaderFactory = exports.ObjectCountLoader = exports.ObjectLoader = void 0;
30
30
  const dataloader_1 = __importDefault(require("dataloader"));
31
31
  const ent_1 = require("../ent");
32
32
  const clause = __importStar(require("../clause"));
33
33
  const logger_1 = require("../logger");
34
+ const clause_1 = require("../clause");
34
35
  const loader_1 = require("./loader");
35
36
  const memoizee_1 = __importDefault(require("memoizee"));
36
- async function loadRowsForLoader(options, ids, context) {
37
+ async function loadRowsForIDLoader(options, ids, context) {
37
38
  let col = options.key;
38
- let cls = clause.In(col, ...ids);
39
- if (options.clause) {
40
- let optionClause;
41
- if (typeof options.clause === "function") {
42
- optionClause = options.clause();
43
- }
44
- else {
45
- optionClause = options.clause;
46
- }
47
- if (optionClause) {
48
- // @ts-expect-error id/string mismatch
49
- cls = clause.And(cls, optionClause);
50
- }
51
- }
39
+ const cls = (0, clause_1.getCombinedClause)(options, clause.In(col, ...ids));
52
40
  const rowOptions = {
53
41
  ...options,
54
42
  clause: cls,
@@ -75,6 +63,29 @@ async function loadRowsForLoader(options, ids, context) {
75
63
  }
76
64
  return result;
77
65
  }
66
+ async function loadRowsForClauseLoader(options, clause) {
67
+ const rowOptions = {
68
+ ...options,
69
+ // @ts-expect-error clause in LoadRowOptions doesn't take templatized version of Clause
70
+ clause: (0, clause_1.getCombinedClause)(options, clause),
71
+ };
72
+ return (await (0, ent_1.loadRows)(rowOptions));
73
+ }
74
+ async function loadCountForClauseLoader(options, clause) {
75
+ const rowOptions = {
76
+ ...options,
77
+ // @ts-expect-error clause in LoadRowOptions doesn't take templatized version of Clause
78
+ clause: (0, clause_1.getCombinedClause)(options, clause),
79
+ };
80
+ const row = await (0, ent_1.loadRow)({
81
+ ...rowOptions,
82
+ fields: ["count(*) as count"],
83
+ });
84
+ if (!row) {
85
+ return 0;
86
+ }
87
+ return parseInt(row.count, 10);
88
+ }
78
89
  // optional clause...
79
90
  // so ObjectLoaderFactory and createDataLoader need to take a new optional field which is a clause that's always added here
80
91
  // and we need a disableTransform which skips loader completely and uses loadRow...
@@ -89,9 +100,66 @@ function createDataLoader(options) {
89
100
  return [];
90
101
  }
91
102
  // context not needed because we're creating a loader which has its own cache which is being used here
92
- return loadRowsForLoader(options, ids);
103
+ return loadRowsForIDLoader(options, ids);
93
104
  }, loaderOptions);
94
105
  }
106
+ class clauseCacheMap {
107
+ constructor(options, count) {
108
+ this.options = options;
109
+ this.count = count;
110
+ this.m = new Map();
111
+ }
112
+ get(key) {
113
+ const key2 = key.instanceKey();
114
+ const ret = this.m.get(key2);
115
+ if (ret) {
116
+ (0, logger_1.log)("cache", {
117
+ "dataloader-cache-hit": key2 + (this.count ? ":count" : ""),
118
+ "tableName": this.options.tableName,
119
+ });
120
+ }
121
+ return ret;
122
+ }
123
+ set(key, value) {
124
+ return this.m.set(key.instanceKey(), value);
125
+ }
126
+ delete(key) {
127
+ return this.m.delete(key.instanceKey());
128
+ }
129
+ clear() {
130
+ return this.m.clear();
131
+ }
132
+ }
133
+ function createClauseDataLoder(options) {
134
+ return new dataloader_1.default(async (clauses) => {
135
+ if (!clauses.length) {
136
+ return [];
137
+ }
138
+ const ret = [];
139
+ for await (const clause of clauses) {
140
+ const data = await loadRowsForClauseLoader(options, clause);
141
+ ret.push(data);
142
+ }
143
+ return ret;
144
+ }, {
145
+ cacheMap: new clauseCacheMap(options),
146
+ });
147
+ }
148
+ function createClauseCountDataLoader(options) {
149
+ return new dataloader_1.default(async (clauses) => {
150
+ if (!clauses.length) {
151
+ return [];
152
+ }
153
+ const ret = [];
154
+ for await (const clause of clauses) {
155
+ const data = await loadCountForClauseLoader(options, clause);
156
+ ret.push(data);
157
+ }
158
+ return ret;
159
+ }, {
160
+ cacheMap: new clauseCacheMap(options, true),
161
+ });
162
+ }
95
163
  class ObjectLoader {
96
164
  constructor(options, context, toPrime) {
97
165
  this.options = options;
@@ -101,7 +169,8 @@ class ObjectLoader {
101
169
  console.trace();
102
170
  }
103
171
  if (context) {
104
- this.loader = createDataLoader(options);
172
+ this.idLoader = createDataLoader(options);
173
+ this.clauseLoader = createClauseDataLoder(options);
105
174
  }
106
175
  this.memoizedInitPrime = (0, memoizee_1.default)(this.initPrime.bind(this));
107
176
  }
@@ -123,11 +192,17 @@ class ObjectLoader {
123
192
  this.primedLoaders = primedLoaders;
124
193
  }
125
194
  async load(key) {
195
+ if (typeof key === "string" || typeof key === "number") {
196
+ return this.loadID(key);
197
+ }
198
+ return this.loadClause(key);
199
+ }
200
+ async loadID(key) {
126
201
  // simple case. we get parallelization etc
127
- if (this.loader) {
202
+ if (this.idLoader) {
128
203
  this.memoizedInitPrime();
129
204
  // prime the result if we got primable loaders
130
- const result = await this.loader.load(key);
205
+ const result = await this.idLoader.load(key);
131
206
  if (result && this.primedLoaders) {
132
207
  for (const [key, loader] of this.primedLoaders) {
133
208
  const value = result[key];
@@ -138,19 +213,7 @@ class ObjectLoader {
138
213
  }
139
214
  return result;
140
215
  }
141
- let cls = clause.Eq(this.options.key, key);
142
- if (this.options.clause) {
143
- let optionClause;
144
- if (typeof this.options.clause === "function") {
145
- optionClause = this.options.clause();
146
- }
147
- else {
148
- optionClause = this.options.clause;
149
- }
150
- if (optionClause) {
151
- cls = clause.And(cls, optionClause);
152
- }
153
- }
216
+ const cls = (0, clause_1.getCombinedClause)(this.options, clause.Eq(this.options.key, key));
154
217
  const rowOptions = {
155
218
  ...this.options,
156
219
  clause: cls,
@@ -158,22 +221,50 @@ class ObjectLoader {
158
221
  };
159
222
  return (0, ent_1.loadRow)(rowOptions);
160
223
  }
224
+ async loadClause(key) {
225
+ if (this.clauseLoader) {
226
+ return this.clauseLoader.load(key);
227
+ }
228
+ return loadRowsForClauseLoader(this.options, key);
229
+ }
161
230
  clearAll() {
162
- this.loader && this.loader.clearAll();
231
+ this.idLoader && this.idLoader.clearAll();
232
+ this.clauseLoader && this.clauseLoader.clearAll();
163
233
  }
164
234
  async loadMany(keys) {
165
- if (this.loader) {
235
+ if (!keys.length) {
236
+ return [];
237
+ }
238
+ if (typeof keys[0] === "string" || typeof keys[0] === "number") {
239
+ return this.loadIDMany(keys);
240
+ }
241
+ return this.loadClauseMany(keys);
242
+ }
243
+ loadIDMany(keys) {
244
+ if (this.idLoader) {
166
245
  // @ts-expect-error TODO?
167
- return this.loader.loadMany(keys);
246
+ return this.idLoader.loadMany(keys);
247
+ }
248
+ return loadRowsForIDLoader(this.options, keys, this.context);
249
+ }
250
+ async loadClauseMany(keys) {
251
+ if (this.clauseLoader) {
252
+ // @ts-expect-error TODO?
253
+ return this.clauseLoader.loadMany(keys);
254
+ }
255
+ const res = [];
256
+ for await (const key of keys) {
257
+ const rows = await loadRowsForClauseLoader(this.options, key);
258
+ res.push(rows);
168
259
  }
169
- return loadRowsForLoader(this.options, keys, this.context);
260
+ return res;
170
261
  }
171
262
  prime(data) {
172
263
  // we have this data from somewhere else, prime it in the c
173
- if (this.loader) {
264
+ if (this.idLoader) {
174
265
  const col = this.options.key;
175
266
  const key = data[col];
176
- this.loader.prime(key, data);
267
+ this.idLoader.prime(key, data);
177
268
  }
178
269
  }
179
270
  // prime this loader and any other loaders it's aware of
@@ -190,6 +281,43 @@ class ObjectLoader {
190
281
  }
191
282
  }
192
283
  exports.ObjectLoader = ObjectLoader;
284
+ class ObjectCountLoader {
285
+ constructor(options, context) {
286
+ this.options = options;
287
+ this.context = context;
288
+ if (context) {
289
+ this.loader = createClauseCountDataLoader(options);
290
+ }
291
+ }
292
+ getOptions() {
293
+ return this.options;
294
+ }
295
+ async load(key) {
296
+ if (this.loader) {
297
+ return this.loader.load(key);
298
+ }
299
+ return loadCountForClauseLoader(this.options, key);
300
+ }
301
+ clearAll() {
302
+ this.loader && this.loader.clearAll();
303
+ }
304
+ async loadMany(keys) {
305
+ if (!keys.length) {
306
+ return [];
307
+ }
308
+ if (this.loader) {
309
+ // @ts-expect-error
310
+ return this.loader.loadMany(keys);
311
+ }
312
+ const res = [];
313
+ for await (const key of keys) {
314
+ const r = await loadCountForClauseLoader(this.options, key);
315
+ res.push(r);
316
+ }
317
+ return res;
318
+ }
319
+ }
320
+ exports.ObjectCountLoader = ObjectCountLoader;
193
321
  // NOTE: if not querying for all columns
194
322
  // have to query for the id field as one of the fields
195
323
  // because it's used to maintain sort order of the queried ids
@@ -213,6 +341,15 @@ class ObjectLoaderFactory {
213
341
  return new ObjectLoader(this.options, context, this.toPrime);
214
342
  }, context);
215
343
  }
344
+ createTypedLoader(context) {
345
+ const loader = this.createLoader(context);
346
+ return loader;
347
+ }
348
+ createCountLoader(context) {
349
+ return (0, loader_1.getCustomLoader)(`${this.name}:count_loader`, () => {
350
+ return new ObjectCountLoader(this.options, context);
351
+ }, context);
352
+ }
216
353
  // keep track of loaders to prime. needs to be done not in the constructor
217
354
  // because there's usually self references here
218
355
  addToPrime(factory) {
@@ -8,7 +8,7 @@ declare class QueryDirectLoader<K extends any> implements Loader<K, Data[]> {
8
8
  context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined;
9
9
  private memoizedInitPrime;
10
10
  private primedLoaders;
11
- constructor(options: QueryOptions, queryOptions?: Partial<Pick<import("../base").QueryableDataOptions, "limit" | "orderby" | "clause">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
11
+ constructor(options: QueryOptions, queryOptions?: Partial<Pick<import("../base").QueryableDataOptions, "clause" | "limit" | "orderby">> | undefined, context?: Context<import("../base").Viewer<import("../base").Ent<any> | null, ID | null>> | undefined);
12
12
  private initPrime;
13
13
  load(id: K): Promise<Data[]>;
14
14
  clearAll(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.0-alpha123",
3
+ "version": "0.1.0-alpha124",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",