@snowtop/ent 0.1.0-alpha73 → 0.1.0-alpha76

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
@@ -5,16 +5,23 @@ export interface Loader<T, V> {
5
5
  loadMany?(keys: T[]): Promise<(V | null)[]>;
6
6
  clearAll(): any;
7
7
  }
8
+ interface LoaderWithLoadMany<T, V> extends Loader<T, V> {
9
+ loadMany(keys: T[]): Promise<(V | null)[]>;
10
+ }
8
11
  export interface LoaderFactory<T, V> {
9
12
  name: string;
10
13
  createLoader(context?: Context): Loader<T, V>;
11
14
  }
15
+ interface LoaderFactoryWithLoaderMany<T, V> extends LoaderFactory<T, V> {
16
+ createLoader(context?: Context): LoaderWithLoadMany<T, V>;
17
+ }
12
18
  export interface ConfigurableLoaderFactory<T, V> extends LoaderFactory<T, V> {
13
19
  createConfigurableLoader(options: EdgeQueryableDataOptions, context?: Context): Loader<T, V>;
14
20
  }
15
21
  export declare type EdgeQueryableDataOptions = Partial<Pick<QueryableDataOptions, "limit" | "orderby" | "clause">>;
16
22
  export interface PrimableLoader<T, V> extends Loader<T, V> {
17
23
  prime(d: Data): void;
24
+ primeAll?(d: Data): void;
18
25
  }
19
26
  interface cache {
20
27
  getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
@@ -23,6 +30,7 @@ interface cache {
23
30
  primeCache(options: queryOptions, rows: Data[]): void;
24
31
  primeCache(options: queryOptions, rows: Data): void;
25
32
  clearCache(): void;
33
+ getEntCache(): Map<string, Ent | Error | null>;
26
34
  }
27
35
  interface queryOptions {
28
36
  fields: string[];
@@ -89,15 +97,15 @@ interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer>
89
97
  loaderFactory: LoaderFactoryWithOptions;
90
98
  ent: EntConstructor<TEnt, TViewer>;
91
99
  }
92
- interface LoaderFactoryWithOptions extends LoaderFactory<any, Data | null> {
100
+ interface LoaderFactoryWithOptions extends LoaderFactoryWithLoaderMany<any, Data | null> {
93
101
  options?: SelectDataOptions;
94
102
  }
95
103
  export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends LoadableEntOptions<TEnt, TViewer>, SelectBaseDataOptions {
96
104
  fieldPrivacy?: Map<string, PrivacyPolicy>;
97
105
  }
98
106
  export interface SelectCustomDataOptions extends SelectBaseDataOptions {
99
- clause?: clause.Clause;
100
- loaderFactory?: LoaderFactoryWithOptions;
107
+ loaderFactory: LoaderFactoryWithOptions;
108
+ prime?: boolean;
101
109
  }
102
110
  export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends SelectCustomDataOptions {
103
111
  ent: EntConstructor<TEnt, TViewer>;
package/core/clause.d.ts CHANGED
@@ -22,6 +22,18 @@ declare class simpleClause implements Clause {
22
22
  logValues(): any[];
23
23
  instanceKey(): string;
24
24
  }
25
+ export declare class inClause implements Clause {
26
+ private col;
27
+ private value;
28
+ private type;
29
+ static getPostgresInClauseValuesThreshold(): number;
30
+ constructor(col: string, value: any[], type?: string);
31
+ clause(idx: number): string;
32
+ columns(): string[];
33
+ values(): any[];
34
+ logValues(): any[];
35
+ instanceKey(): string;
36
+ }
25
37
  declare class compositeClause implements Clause {
26
38
  private clauses;
27
39
  private sep;
@@ -86,6 +98,7 @@ export declare function And(...args: Clause[]): compositeClause;
86
98
  export declare function AndOptional(...args: (Clause | undefined)[]): Clause;
87
99
  export declare function Or(...args: Clause[]): compositeClause;
88
100
  export declare function In(col: string, ...values: any): Clause;
101
+ export declare function In(col: string, values: any[], type?: string): Clause;
89
102
  interface TsQuery {
90
103
  language: "english" | "french" | "german" | "simple";
91
104
  value: string;
package/core/clause.js CHANGED
@@ -19,7 +19,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
19
19
  return result;
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- 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.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 = void 0;
22
+ 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.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;
23
23
  const db_1 = __importStar(require("./db"));
24
24
  function isSensitive(val) {
25
25
  return (val !== null &&
@@ -213,17 +213,37 @@ class postgresArrayOperatorList extends postgresArrayOperator {
213
213
  }
214
214
  }
215
215
  class inClause {
216
- constructor(col, value) {
216
+ constructor(col, value, type = "uuid") {
217
217
  this.col = col;
218
218
  this.value = value;
219
+ this.type = type;
220
+ }
221
+ static getPostgresInClauseValuesThreshold() {
222
+ return 70;
219
223
  }
220
224
  clause(idx) {
221
- const dialect = db_1.default.getDialect();
225
+ // do a simple = when only one item
226
+ if (this.value.length === 1) {
227
+ return new simpleClause(this.col, this.value[0], "=").clause(idx);
228
+ }
229
+ const postgres = db_1.default.getDialect() === db_1.Dialect.Postgres;
230
+ const postgresValuesList = postgres &&
231
+ this.value.length >= inClause.getPostgresInClauseValuesThreshold();
222
232
  let indices;
223
- if (dialect === db_1.Dialect.Postgres) {
233
+ if (postgres) {
224
234
  indices = [];
225
235
  for (let i = 0; i < this.value.length; i++) {
226
- indices.push(`$${idx}`);
236
+ if (postgresValuesList) {
237
+ if (i === 0) {
238
+ indices.push(`($${idx}::${this.type})`);
239
+ }
240
+ else {
241
+ indices.push(`($${idx})`);
242
+ }
243
+ }
244
+ else {
245
+ indices.push(`$${idx}`);
246
+ }
227
247
  idx++;
228
248
  }
229
249
  }
@@ -231,7 +251,11 @@ class inClause {
231
251
  indices = new Array(this.value.length);
232
252
  indices.fill("?", 0);
233
253
  }
234
- const inValue = indices.join(", ");
254
+ let inValue = indices.join(", ");
255
+ // wrap in VALUES list for postgres...
256
+ if (postgresValuesList) {
257
+ inValue = `VALUES${inValue}`;
258
+ }
235
259
  return `${this.col} IN (${inValue})`;
236
260
  // TODO we need to return idx at end to query builder...
237
261
  // or anything that's doing a composite query so next clause knows where to start
@@ -243,25 +267,15 @@ class inClause {
243
267
  }
244
268
  values() {
245
269
  const result = [];
246
- for (const value of this.value) {
247
- if (isSensitive(value)) {
248
- result.push(value.value());
249
- }
250
- else {
251
- result.push(value);
252
- }
270
+ for (let value of this.value) {
271
+ result.push(rawValue(value));
253
272
  }
254
273
  return result;
255
274
  }
256
275
  logValues() {
257
276
  const result = [];
258
- for (const value of this.value) {
259
- if (isSensitive(value)) {
260
- result.push(value.logValue());
261
- }
262
- else {
263
- result.push(value);
264
- }
277
+ for (let value of this.value) {
278
+ result.push(isSensitive(value) ? value.logValue() : value);
265
279
  }
266
280
  return result;
267
281
  }
@@ -269,6 +283,7 @@ class inClause {
269
283
  return `in:${this.col}:${this.values().join(",")}`;
270
284
  }
271
285
  }
286
+ exports.inClause = inClause;
272
287
  class compositeClause {
273
288
  constructor(clauses, sep) {
274
289
  this.clauses = clauses;
@@ -486,9 +501,15 @@ function Or(...args) {
486
501
  return new compositeClause(args, " OR ");
487
502
  }
488
503
  exports.Or = Or;
489
- // TODO this breaks if values.length ===1 and array. todo fix
490
- function In(col, ...values) {
491
- return new inClause(col, values);
504
+ function In(...args) {
505
+ if (args.length < 2) {
506
+ throw new Error(`invalid args passed to In`);
507
+ }
508
+ // 2nd overload
509
+ if (Array.isArray(args[1])) {
510
+ return new inClause(args[0], args[1], args[2]);
511
+ }
512
+ return new inClause(args[0], args.slice(1));
492
513
  }
493
514
  exports.In = In;
494
515
  // if string defaults to english
package/core/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { Viewer, Data, Loader } from "./base";
2
+ import { Viewer, Data, Loader, Ent } from "./base";
3
3
  import { IncomingMessage, ServerResponse } from "http";
4
4
  import * as clause from "./clause";
5
5
  import { Context } from "./base";
@@ -14,12 +14,14 @@ export declare class ContextCache {
14
14
  getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
15
15
  private itemMap;
16
16
  private listMap;
17
+ private entCache;
17
18
  private getkey;
18
19
  getCachedRows(options: queryOptions): Data[] | null;
19
20
  getCachedRow(options: queryOptions): Data | null;
20
21
  primeCache(options: queryOptions, rows: Data[]): void;
21
22
  primeCache(options: queryOptions, rows: Data): void;
22
23
  clearCache(): void;
24
+ getEntCache(): Map<string, Error | Ent<any> | null>;
23
25
  }
24
26
  interface queryOptions {
25
27
  fields: string[];
package/core/context.js CHANGED
@@ -8,6 +8,7 @@ class ContextCache {
8
8
  // we have a per-table map to make it easier to purge and have less things to compare with
9
9
  this.itemMap = new Map();
10
10
  this.listMap = new Map();
11
+ this.entCache = new Map();
11
12
  }
12
13
  getLoader(name, create) {
13
14
  let l = this.loaders.get(name);
@@ -82,6 +83,10 @@ class ContextCache {
82
83
  this.loaders.clear();
83
84
  this.itemMap.clear();
84
85
  this.listMap.clear();
86
+ this.entCache.clear();
87
+ }
88
+ getEntCache() {
89
+ return this.entCache;
85
90
  }
86
91
  }
87
92
  exports.ContextCache = ContextCache;
package/core/ent.d.ts CHANGED
@@ -5,14 +5,24 @@ import * as clause from "./clause";
5
5
  import { Builder } from "../action";
6
6
  import DataLoader from "dataloader";
7
7
  import { GlobalSchema } from "../schema/";
8
+ export declare function getEntKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): string;
8
9
  export declare function loadEnt<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt | null>;
9
10
  export declare function loadEntViaKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, key: any, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt | null>;
10
11
  export declare function loadEntX<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt>;
11
12
  export declare function loadEntXViaKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, key: any, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt>;
13
+ /**
14
+ * @deprecated use loadCustomEnts
15
+ */
12
16
  export declare function loadEntFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, clause: clause.Clause): Promise<TEnt | null>;
17
+ /**
18
+ * @deprecated use loadCustomEnts
19
+ */
13
20
  export declare function loadEntXFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, clause: clause.Clause): Promise<TEnt>;
14
21
  export declare function loadEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, ...ids: ID[]): Promise<Map<ID, TEnt>>;
15
22
  export declare function loadEntsList<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, ...ids: ID[]): Promise<TEnt[]>;
23
+ /**
24
+ * @deperecated use loadCustomEnts
25
+ */
16
26
  export declare function loadEntsFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, clause: clause.Clause, options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
17
27
  export declare function loadCustomEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadCustomEntOptions<TEnt, TViewer>, query: CustomQuery): Promise<TEnt[]>;
18
28
  interface parameterizedQueryOptions {
@@ -210,7 +220,6 @@ interface loadEdgeForIDOptions<T extends AssocEdge> extends loadCustomEdgesOptio
210
220
  export declare function loadEdgeForID2<T extends AssocEdge>(options: loadEdgeForIDOptions<T>): Promise<T | undefined>;
211
221
  export declare function loadNodesByEdge<T extends Ent>(viewer: Viewer, id1: ID, edgeType: string, options: LoadEntOptions<T>): Promise<T[]>;
212
222
  export declare function applyPrivacyPolicyForRow<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data | null): Promise<TEnt | null>;
213
- export declare function applyPrivacyPolicyForRowX<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data): Promise<TEnt>;
214
223
  export declare function applyPrivacyPolicyForRows<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, rows: Data[], options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
215
224
  export declare function getEdgeTypeInGroup<T extends string>(viewer: Viewer, id1: ID, id2: ID, m: Map<T, string>): Promise<[T, AssocEdge] | undefined>;
216
225
  export {};
package/core/ent.js CHANGED
@@ -22,8 +22,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.defaultEdgeQueryOptions = exports.DefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.DeleteNodeOperation = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.EdgeOperation = exports.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = exports.EditNodeOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = void 0;
26
- exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRowX = void 0;
25
+ exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.defaultEdgeQueryOptions = exports.DefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.DeleteNodeOperation = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.EdgeOperation = exports.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = exports.EditNodeOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = exports.getEntKey = void 0;
26
+ exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRow = void 0;
27
27
  const db_1 = __importStar(require("./db"));
28
28
  const privacy_1 = require("./privacy");
29
29
  const clause = __importStar(require("./clause"));
@@ -87,10 +87,58 @@ function createDataLoader(options) {
87
87
  return result;
88
88
  }, loaderOptions);
89
89
  }
90
+ function getEntKey(viewer, id, options) {
91
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
92
+ }
93
+ exports.getEntKey = getEntKey;
94
+ // fetches the ent from cache if cache exists.
95
+ function entFromCacheMaybe(viewer, id, options) {
96
+ const cache = viewer.context?.cache?.getEntCache();
97
+ if (!cache) {
98
+ return {};
99
+ }
100
+ const key = getEntKey(viewer, id, options);
101
+ const r = cache.get(key);
102
+ if (r !== undefined) {
103
+ (0, logger_1.log)("cache", {
104
+ "ent-cache-hit": key,
105
+ });
106
+ }
107
+ return {
108
+ key,
109
+ ent: r instanceof Error ? undefined : r,
110
+ error: r instanceof Error ? r : undefined,
111
+ cache,
112
+ };
113
+ }
90
114
  // Ent accessors
115
+ async function applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info) {
116
+ const ent = await applyPrivacyPolicyForRow(viewer, options, row);
117
+ if (info.cache && info.key) {
118
+ info.cache.set(info.key, ent);
119
+ }
120
+ return ent instanceof Error ? null : ent;
121
+ }
122
+ async function applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info) {
123
+ const ent = await applyPrivacyPolicyForRowImpl(viewer, options, row);
124
+ if (info.cache && info.key) {
125
+ info.cache.set(info.key, ent);
126
+ }
127
+ if (ent instanceof Error) {
128
+ throw ent;
129
+ }
130
+ if (ent === null) {
131
+ throw new Error(`applyPrivacyPolicyForRowImpl returned null when it shouldn't. ent error`);
132
+ }
133
+ return ent;
134
+ }
91
135
  async function loadEnt(viewer, id, options) {
136
+ const info = entFromCacheMaybe(viewer, id, options);
137
+ if (info.ent !== undefined) {
138
+ return info.ent;
139
+ }
92
140
  const row = await options.loaderFactory.createLoader(viewer.context).load(id);
93
- return await applyPrivacyPolicyForRow(viewer, options, row);
141
+ return applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
94
142
  }
95
143
  exports.loadEnt = loadEnt;
96
144
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
@@ -99,16 +147,32 @@ async function loadEntViaKey(viewer, key, options) {
99
147
  const row = await options.loaderFactory
100
148
  .createLoader(viewer.context)
101
149
  .load(key);
102
- return await applyPrivacyPolicyForRow(viewer, options, row);
150
+ if (!row) {
151
+ return null;
152
+ }
153
+ // TODO every row.id needs to be audited...
154
+ // https://github.com/lolopinto/ent/issues/1064
155
+ const info = entFromCacheMaybe(viewer, row.id, options);
156
+ if (info.ent !== undefined) {
157
+ return info.ent;
158
+ }
159
+ return applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
103
160
  }
104
161
  exports.loadEntViaKey = loadEntViaKey;
105
162
  async function loadEntX(viewer, id, options) {
163
+ const info = entFromCacheMaybe(viewer, id, options);
164
+ if (info.error !== undefined) {
165
+ throw info.error;
166
+ }
167
+ if (info.ent !== undefined && info.ent !== null) {
168
+ return info.ent;
169
+ }
106
170
  const row = await options.loaderFactory.createLoader(viewer.context).load(id);
107
171
  if (!row) {
108
172
  // todo make this better
109
173
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${id}`);
110
174
  }
111
- return await applyPrivacyPolicyForRowX(viewer, options, row);
175
+ return applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info);
112
176
  }
113
177
  exports.loadEntX = loadEntX;
114
178
  async function loadEntXViaKey(viewer, key, options) {
@@ -119,9 +183,19 @@ async function loadEntXViaKey(viewer, key, options) {
119
183
  // todo make this better
120
184
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
121
185
  }
122
- return await applyPrivacyPolicyForRowX(viewer, options, row);
186
+ const info = entFromCacheMaybe(viewer, row.id, options);
187
+ if (info.error !== undefined) {
188
+ throw info.error;
189
+ }
190
+ if (info.ent !== undefined && info.ent !== null) {
191
+ return info.ent;
192
+ }
193
+ return applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info);
123
194
  }
124
195
  exports.loadEntXViaKey = loadEntXViaKey;
196
+ /**
197
+ * @deprecated use loadCustomEnts
198
+ */
125
199
  async function loadEntFromClause(viewer, options, clause) {
126
200
  const rowOptions = {
127
201
  ...options,
@@ -129,12 +203,15 @@ async function loadEntFromClause(viewer, options, clause) {
129
203
  context: viewer.context,
130
204
  };
131
205
  const row = await loadRow(rowOptions);
132
- return await applyPrivacyPolicyForRow(viewer, options, row);
206
+ return applyPrivacyPolicyForRow(viewer, options, row);
133
207
  }
134
208
  exports.loadEntFromClause = loadEntFromClause;
135
209
  // same as loadEntFromClause
136
210
  // only works for ents where primary key is "id"
137
211
  // use loadEnt with a loaderFactory if different
212
+ /**
213
+ * @deprecated use loadCustomEnts
214
+ */
138
215
  async function loadEntXFromClause(viewer, options, clause) {
139
216
  const rowOptions = {
140
217
  ...options,
@@ -149,37 +226,66 @@ async function loadEnts(viewer, options, ...ids) {
149
226
  if (!ids.length) {
150
227
  return new Map();
151
228
  }
152
- let loaded = false;
153
- let rows = [];
154
- // TODO loadMany everywhere
155
- const l = options.loaderFactory.createLoader(viewer.context);
156
- if (l.loadMany) {
157
- loaded = true;
158
- rows = await l.loadMany(ids);
159
- }
160
- // TODO rewrite all of this
229
+ // result
161
230
  let m = new Map();
162
- if (loaded) {
163
- let rows2 = [];
164
- for (const row of rows) {
165
- if (!row) {
166
- continue;
231
+ const cache = viewer.context?.cache?.getEntCache();
232
+ let toFetch = [];
233
+ if (cache) {
234
+ for (const id of ids) {
235
+ const key = getEntKey(viewer, id, options);
236
+ const ent = cache.get(key);
237
+ if (ent !== undefined) {
238
+ (0, logger_1.log)("cache", {
239
+ "ent-cache-hit": key,
240
+ });
241
+ if (ent === null) {
242
+ // TODO this should return null if not loadable...
243
+ // https://github.com/lolopinto/ent/issues/1070
244
+ continue;
245
+ }
246
+ // @ts-ignore
247
+ m.set(id, ent);
167
248
  }
168
- if (row instanceof Error) {
169
- throw row;
249
+ else {
250
+ toFetch.push(id);
170
251
  }
171
- rows2.push(row);
172
252
  }
173
- m = await applyPrivacyPolicyForRows(viewer, rows2, options);
174
253
  }
175
254
  else {
176
- m = await loadEntsFromClause(viewer,
177
- // this is always "id" if not using an ObjectLoaderFactory
178
- clause.In("id", ...ids), options);
255
+ toFetch = ids;
256
+ }
257
+ // all in ent cache!
258
+ if (!toFetch.length) {
259
+ return m;
260
+ }
261
+ const l = options.loaderFactory.createLoader(viewer.context);
262
+ const rows = await l.loadMany(toFetch);
263
+ let rows2 = [];
264
+ for (const row of rows) {
265
+ if (!row) {
266
+ continue;
267
+ }
268
+ if (row instanceof Error) {
269
+ throw row;
270
+ }
271
+ rows2.push(row);
272
+ }
273
+ const m2 = await applyPrivacyPolicyForRows(viewer, rows2, options);
274
+ for (const row of rows2) {
275
+ const id = row[options.loaderFactory.options?.key || "id"];
276
+ const ent = m2.get(id);
277
+ if (cache) {
278
+ // put back in cache...
279
+ // store null for rows that can't be seen
280
+ cache.set(getEntKey(viewer, id, options), ent ?? null);
281
+ }
282
+ if (ent !== undefined) {
283
+ // TODO this should return null if not loadable...?
284
+ // TODO https://github.com/lolopinto/ent/issues/1070
285
+ m.set(id, ent);
286
+ }
179
287
  }
180
288
  return m;
181
- // TODO do we want to change this to be a map not a list so that it's easy to check for existence?
182
- // TODO eventually this should be doing a cache then db queyr and maybe depend on dataloader to get all the results at once
183
289
  }
184
290
  exports.loadEnts = loadEnts;
185
291
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -198,6 +304,9 @@ async function loadEntsList(viewer, options, ...ids) {
198
304
  exports.loadEntsList = loadEntsList;
199
305
  // we return a map here so that any sorting for queries that exist
200
306
  // can be done in O(N) time
307
+ /**
308
+ * @deperecated use loadCustomEnts
309
+ */
201
310
  async function loadEntsFromClause(viewer, clause, options) {
202
311
  const rowOptions = {
203
312
  ...options,
@@ -212,8 +321,19 @@ async function loadCustomEnts(viewer, options, query) {
212
321
  const rows = await loadCustomData(options, query, viewer.context);
213
322
  const result = new Array(rows.length);
214
323
  await Promise.all(rows.map(async (row, idx) => {
215
- const ent = new options.ent(viewer, row);
216
- let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent, row, options);
324
+ // TODO what if key is different
325
+ // TODO https://github.com/lolopinto/ent/issues/1064
326
+ const info = entFromCacheMaybe(viewer, row.id, options);
327
+ if (info.ent !== undefined) {
328
+ if (info.ent === null) {
329
+ // we're done here
330
+ return;
331
+ }
332
+ // @ts-ignore
333
+ result[idx] = info.ent;
334
+ return;
335
+ }
336
+ const privacyEnt = await applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
217
337
  if (privacyEnt) {
218
338
  result[idx] = privacyEnt;
219
339
  }
@@ -254,11 +374,25 @@ function isParameterizedQuery(opts) {
254
374
  * }) // doesn't change the query
255
375
  */
256
376
  async function loadCustomData(options, query, context) {
257
- function getClause(cls) {
258
- if (options.clause && options.loaderFactory?.options?.clause) {
259
- throw new Error(`cannot pass both options.clause && optsions.loaderFactory.options.clause`);
377
+ const rows = await loadCustomDataImpl(options, query, context);
378
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
379
+ if (options.prime) {
380
+ const loader = options.loaderFactory.createLoader(context);
381
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
382
+ for (const row of rows) {
383
+ loader.primeAll(row);
384
+ }
260
385
  }
261
- let optClause = options.clause || options.loaderFactory?.options?.clause;
386
+ }
387
+ return rows;
388
+ }
389
+ exports.loadCustomData = loadCustomData;
390
+ function isPrimableLoader(loader) {
391
+ return loader != undefined;
392
+ }
393
+ async function loadCustomDataImpl(options, query, context) {
394
+ function getClause(cls) {
395
+ let optClause = options.loaderFactory?.options?.clause;
262
396
  if (typeof optClause === "function") {
263
397
  optClause = optClause();
264
398
  }
@@ -269,7 +403,7 @@ async function loadCustomData(options, query, context) {
269
403
  }
270
404
  if (typeof query === "string") {
271
405
  // no caching, perform raw query
272
- return await performRawQuery(query, [], []);
406
+ return performRawQuery(query, [], []);
273
407
  }
274
408
  else if (isClause(query)) {
275
409
  // if a Clause is passed in and we have a default clause
@@ -277,7 +411,7 @@ async function loadCustomData(options, query, context) {
277
411
  // if we want to disableTransformations, need to indicate that with
278
412
  // disableTransformations option
279
413
  // this will have rudimentary caching but nothing crazy
280
- return await loadRows({
414
+ return loadRows({
281
415
  ...options,
282
416
  clause: getClause(query),
283
417
  context: context,
@@ -285,7 +419,7 @@ async function loadCustomData(options, query, context) {
285
419
  }
286
420
  else if (isParameterizedQuery(query)) {
287
421
  // no caching, perform raw query
288
- return await performRawQuery(query.query, query.values || [], query.logValues);
422
+ return performRawQuery(query.query, query.values || [], query.logValues);
289
423
  }
290
424
  else {
291
425
  let cls = query.clause;
@@ -293,7 +427,7 @@ async function loadCustomData(options, query, context) {
293
427
  cls = getClause(cls);
294
428
  }
295
429
  // this will have rudimentary caching but nothing crazy
296
- return await loadRows({
430
+ return loadRows({
297
431
  ...query,
298
432
  ...options,
299
433
  context: context,
@@ -301,15 +435,20 @@ async function loadCustomData(options, query, context) {
301
435
  });
302
436
  }
303
437
  }
304
- exports.loadCustomData = loadCustomData;
305
438
  // Derived ents
439
+ // no ent caching
306
440
  async function loadDerivedEnt(viewer, data, loader) {
307
441
  const ent = new loader(viewer, data);
308
- return await applyPrivacyPolicyForEnt(viewer, ent, data, {
442
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
309
443
  ent: loader,
310
444
  });
445
+ if (r instanceof Error) {
446
+ return null;
447
+ }
448
+ return r;
311
449
  }
312
450
  exports.loadDerivedEnt = loadDerivedEnt;
451
+ // won't have caching yet either
313
452
  async function loadDerivedEntX(viewer, data, loader) {
314
453
  const ent = new loader(viewer, data);
315
454
  return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
@@ -319,18 +458,23 @@ exports.loadDerivedEntX = loadDerivedEntX;
319
458
  // TODO is there a smarter way to not instantiate two objects here?
320
459
  async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
321
460
  if (ent) {
322
- const visible = await (0, privacy_1.applyPrivacyPolicy)(viewer, ent.getPrivacyPolicy(), ent);
323
- if (!visible) {
324
- return null;
461
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
462
+ if (error === null) {
463
+ return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
325
464
  }
326
- return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
465
+ return error;
327
466
  }
328
467
  return null;
329
468
  }
330
469
  async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
331
- // this will throw
332
- await (0, privacy_1.applyPrivacyPolicyX)(viewer, ent.getPrivacyPolicy(), ent);
333
- return doFieldPrivacy(viewer, ent, data, options);
470
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
471
+ if (r instanceof Error) {
472
+ throw r;
473
+ }
474
+ if (r === null) {
475
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
476
+ }
477
+ return r;
334
478
  }
335
479
  async function doFieldPrivacy(viewer, ent, data, options) {
336
480
  if (!options.fieldPrivacy) {
@@ -339,12 +483,12 @@ async function doFieldPrivacy(viewer, ent, data, options) {
339
483
  const promises = [];
340
484
  let somethingChanged = false;
341
485
  for (const [k, policy] of options.fieldPrivacy) {
486
+ const curr = data[k];
487
+ if (curr === null || curr === undefined) {
488
+ continue;
489
+ }
342
490
  promises.push((async () => {
343
491
  // don't do anything if key is null or for some reason missing
344
- const curr = data[k];
345
- if (curr === null || curr === undefined) {
346
- return;
347
- }
348
492
  const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
349
493
  if (!r) {
350
494
  data[k] = null;
@@ -388,42 +532,27 @@ async function loadRow(options) {
388
532
  }
389
533
  const query = buildQuery(options);
390
534
  logQuery(query, options.clause.logValues());
391
- try {
392
- const pool = db_1.default.getInstance().getPool();
393
- const res = await pool.query(query, options.clause.values());
394
- if (res.rowCount != 1) {
395
- if (res.rowCount > 1) {
396
- (0, logger_1.log)("error", "got more than one row for query " + query);
397
- }
398
- return null;
399
- }
400
- // put the row in the cache...
401
- if (cache) {
402
- cache.primeCache(options, res.rows[0]);
535
+ const pool = db_1.default.getInstance().getPool();
536
+ const res = await pool.query(query, options.clause.values());
537
+ if (res.rowCount != 1) {
538
+ if (res.rowCount > 1) {
539
+ (0, logger_1.log)("error", "got more than one row for query " + query);
403
540
  }
404
- return res.rows[0];
405
- }
406
- catch (e) {
407
- // an example of an error being suppressed
408
- // another one. TODO https://github.com/lolopinto/ent/issues/862
409
- (0, logger_1.log)("error", e);
410
541
  return null;
411
542
  }
543
+ // put the row in the cache...
544
+ if (cache) {
545
+ cache.primeCache(options, res.rows[0]);
546
+ }
547
+ return res.rows[0];
412
548
  }
413
549
  exports.loadRow = loadRow;
414
550
  // this always goes to the db, no cache, nothing
415
551
  async function performRawQuery(query, values, logValues) {
416
552
  const pool = db_1.default.getInstance().getPool();
417
553
  logQuery(query, logValues || []);
418
- try {
419
- const res = await pool.queryAll(query, values);
420
- return res.rows;
421
- }
422
- catch (e) {
423
- // TODO need to change every query to catch an error!
424
- (0, logger_1.log)("error", e);
425
- return [];
426
- }
554
+ const res = await pool.queryAll(query, values);
555
+ return res.rows;
427
556
  }
428
557
  exports.performRawQuery = performRawQuery;
429
558
  // TODO this should throw, we can't be hiding errors here
@@ -547,7 +676,7 @@ class EditNodeOperation {
547
676
  optionClause = opts.clause;
548
677
  }
549
678
  if (optionClause) {
550
- cls = clause.And(optionClause, cls);
679
+ cls = clause.And(cls, optionClause);
551
680
  }
552
681
  }
553
682
  const query = buildQuery({
@@ -949,40 +1078,26 @@ function isSyncQueryer(queryer) {
949
1078
  async function mutateRow(queryer, query, values, logValues, options) {
950
1079
  logQuery(query, logValues);
951
1080
  let cache = options.context?.cache;
952
- try {
953
- let res;
954
- if (isSyncQueryer(queryer)) {
955
- res = queryer.execSync(query, values);
956
- }
957
- else {
958
- res = await queryer.exec(query, values);
959
- }
960
- if (cache) {
961
- cache.clearCache();
962
- }
963
- return res;
1081
+ let res;
1082
+ if (isSyncQueryer(queryer)) {
1083
+ res = queryer.execSync(query, values);
1084
+ }
1085
+ else {
1086
+ res = await queryer.exec(query, values);
964
1087
  }
965
- catch (err) {
966
- // TODO:::why is this not rethrowing?
967
- (0, logger_1.log)("error", err);
968
- throw err;
1088
+ if (cache) {
1089
+ cache.clearCache();
969
1090
  }
1091
+ return res;
970
1092
  }
971
1093
  function mutateRowSync(queryer, query, values, logValues, options) {
972
1094
  logQuery(query, logValues);
973
1095
  let cache = options.context?.cache;
974
- try {
975
- const res = queryer.execSync(query, values);
976
- if (cache) {
977
- cache.clearCache();
978
- }
979
- return res;
980
- }
981
- catch (err) {
982
- // TODO:::why is this not rethrowing?
983
- (0, logger_1.log)("error", err);
984
- throw err;
1096
+ const res = queryer.execSync(query, values);
1097
+ if (cache) {
1098
+ cache.clearCache();
985
1099
  }
1100
+ return res;
986
1101
  }
987
1102
  function buildInsertQuery(options, suffix) {
988
1103
  let fields = [];
@@ -1062,6 +1177,9 @@ function buildUpdateQuery(options, suffix) {
1062
1177
  let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
1063
1178
  query = query + options.whereClause.clause(idx);
1064
1179
  values.push(...options.whereClause.values());
1180
+ if (options.fieldsToLog) {
1181
+ logValues.push(...options.whereClause.logValues());
1182
+ }
1065
1183
  if (suffix) {
1066
1184
  query = query + " " + suffix;
1067
1185
  }
@@ -1352,18 +1470,24 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1352
1470
  }
1353
1471
  exports.loadNodesByEdge = loadNodesByEdge;
1354
1472
  async function applyPrivacyPolicyForRow(viewer, options, row) {
1473
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1474
+ return r instanceof Error ? null : r;
1475
+ }
1476
+ exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1477
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1355
1478
  if (!row) {
1356
1479
  return null;
1357
1480
  }
1358
1481
  const ent = new options.ent(viewer, row);
1359
- return await applyPrivacyPolicyForEnt(viewer, ent, row, options);
1482
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1360
1483
  }
1361
- exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1362
1484
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1363
1485
  const ent = new options.ent(viewer, row);
1364
1486
  return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1365
1487
  }
1366
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1488
+ // TODO this needs to be changed to use ent cache as needed...
1489
+ // most current callsites fine not using it
1490
+ // custom_query is one that should be updated
1367
1491
  async function applyPrivacyPolicyForRows(viewer, rows, options) {
1368
1492
  let m = new Map();
1369
1493
  // apply privacy logic
@@ -13,6 +13,7 @@ export declare class ObjectLoader<T> implements Loader<T, Data | null> {
13
13
  clearAll(): void;
14
14
  loadMany(keys: T[]): Promise<Data[]>;
15
15
  prime(data: Data): void;
16
+ primeAll(data: Data): void;
16
17
  }
17
18
  interface ObjectLoaderOptions extends SelectDataOptions {
18
19
  instanceKey?: string;
@@ -53,7 +53,7 @@ function createDataLoader(options) {
53
53
  optionClause = options.clause;
54
54
  }
55
55
  if (optionClause) {
56
- cls = clause.And(optionClause, cls);
56
+ cls = clause.And(cls, optionClause);
57
57
  }
58
58
  }
59
59
  const rowOptions = {
@@ -71,6 +71,9 @@ function createDataLoader(options) {
71
71
  const rows = await (0, ent_1.loadRows)(rowOptions);
72
72
  for (const row of rows) {
73
73
  const id = row[col];
74
+ if (id === undefined) {
75
+ throw new Error(`need to query for column ${col} when using an object loader because the query may not be sorted and we need the id to maintain sort order`);
76
+ }
74
77
  const idx = m.get(id);
75
78
  if (idx === undefined) {
76
79
  throw new Error(`malformed query. got ${id} back but didn't query for it`);
@@ -136,7 +139,7 @@ class ObjectLoader {
136
139
  optionClause = this.options.clause;
137
140
  }
138
141
  if (optionClause) {
139
- cls = clause.And(optionClause, cls);
142
+ cls = clause.And(cls, optionClause);
140
143
  }
141
144
  }
142
145
  const rowOptions = {
@@ -163,7 +166,7 @@ class ObjectLoader {
163
166
  optionClause = this.options.clause;
164
167
  }
165
168
  if (optionClause) {
166
- cls = clause.And(optionClause, cls);
169
+ cls = clause.And(cls, optionClause);
167
170
  }
168
171
  }
169
172
  const rowOptions = {
@@ -181,8 +184,23 @@ class ObjectLoader {
181
184
  this.loader.prime(key, data);
182
185
  }
183
186
  }
187
+ // prime this loader and any other loaders it's aware of
188
+ primeAll(data) {
189
+ this.prime(data);
190
+ if (this.primedLoaders) {
191
+ for (const [key, loader] of this.primedLoaders) {
192
+ const value = data[key];
193
+ if (value !== undefined) {
194
+ loader.prime(data);
195
+ }
196
+ }
197
+ }
198
+ }
184
199
  }
185
200
  exports.ObjectLoader = ObjectLoader;
201
+ // NOTE: if not querying for all columns
202
+ // have to query for the id field as one of the fields
203
+ // because it's used to maintain sort order of the queried ids
186
204
  class ObjectLoaderFactory {
187
205
  constructor(options) {
188
206
  this.options = options;
package/core/privacy.d.ts CHANGED
@@ -183,6 +183,7 @@ export declare class AllowIfSubPolicyAllowsRule implements PrivacyPolicyRule {
183
183
  }
184
184
  export declare function applyPrivacyPolicy(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined): Promise<boolean>;
185
185
  export declare function applyPrivacyPolicyX(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined, throwErr?: () => Error): Promise<boolean>;
186
+ export declare function applyPrivacyPolicyImpl(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined, throwErr?: () => Error): Promise<Error | null>;
186
187
  export declare const AlwaysAllowPrivacyPolicy: PrivacyPolicy;
187
188
  export declare const AlwaysDenyPrivacyPolicy: PrivacyPolicy;
188
189
  export declare const AllowIfViewerPrivacyPolicy: PrivacyPolicy;
package/core/privacy.js CHANGED
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.DelayedResultRule = exports.AllowIfConditionAppliesRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = exports.DenyIfEdgeDoesNotExistRule = exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.DenyIfFuncRule = exports.AllowIfFuncRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedInRule = exports.DenyIfLoggedOutRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = void 0;
3
+ exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyImpl = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.DelayedResultRule = exports.AllowIfConditionAppliesRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = exports.DenyIfEdgeDoesNotExistRule = exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.DenyIfFuncRule = exports.AllowIfFuncRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedInRule = exports.DenyIfLoggedOutRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = void 0;
4
4
  const base_1 = require("./base");
5
5
  const ent_1 = require("./ent");
6
- const logger_1 = require("./logger");
7
6
  // copied from ./base
8
7
  var privacyResult;
9
8
  (function (privacyResult) {
@@ -437,42 +436,42 @@ class AllowIfSubPolicyAllowsRule {
437
436
  }
438
437
  exports.AllowIfSubPolicyAllowsRule = AllowIfSubPolicyAllowsRule;
439
438
  async function applyPrivacyPolicy(v, policy, ent) {
440
- try {
441
- return await applyPrivacyPolicyX(v, policy, ent);
442
- }
443
- catch (e) {
444
- // TODO privacy errors should not throw
445
- // but other expected errors should throw...
446
- // we shouldn't just hide them
447
- (0, logger_1.log)("debug", e);
448
- return false;
449
- }
439
+ const err = await applyPrivacyPolicyImpl(v, policy, ent);
440
+ return err === null;
450
441
  }
451
442
  exports.applyPrivacyPolicy = applyPrivacyPolicy;
452
- // this will throw an exception if fails or return error | null?
453
443
  async function applyPrivacyPolicyX(v, policy, ent, throwErr) {
444
+ const err = await applyPrivacyPolicyImpl(v, policy, ent, throwErr);
445
+ if (err !== null) {
446
+ throw err;
447
+ }
448
+ return true;
449
+ }
450
+ exports.applyPrivacyPolicyX = applyPrivacyPolicyX;
451
+ // this will throw an exception if fails or return error | null?
452
+ async function applyPrivacyPolicyImpl(v, policy, ent, throwErr) {
454
453
  for (const rule of policy.rules) {
455
454
  const res = await rule.apply(v, ent);
456
455
  if (res.result == privacyResult.Allow) {
457
- return true;
456
+ return null;
458
457
  }
459
458
  else if (res.result == privacyResult.Deny) {
460
459
  // specific error throw that
461
460
  if (res.error) {
462
- throw res.error;
461
+ return res.error;
463
462
  }
464
463
  if (res.getError) {
465
- throw res.getError(policy, rule, ent);
464
+ return res.getError(policy, rule, ent);
466
465
  }
467
466
  if (throwErr) {
468
- throw throwErr();
467
+ return throwErr();
469
468
  }
470
- throw new EntPrivacyError(policy, rule, ent);
469
+ return new EntPrivacyError(policy, rule, ent);
471
470
  }
472
471
  }
473
- throw new EntInvalidPrivacyPolicyError(policy, ent);
472
+ return new EntInvalidPrivacyPolicyError(policy, ent);
474
473
  }
475
- exports.applyPrivacyPolicyX = applyPrivacyPolicyX;
474
+ exports.applyPrivacyPolicyImpl = applyPrivacyPolicyImpl;
476
475
  exports.AlwaysAllowPrivacyPolicy = {
477
476
  rules: [exports.AlwaysAllowRule],
478
477
  };
package/core/viewer.js CHANGED
@@ -35,7 +35,7 @@ class IDViewer {
35
35
  return this.ent;
36
36
  }
37
37
  instanceKey() {
38
- return `idViewer: ${this.viewerID}`;
38
+ return `idViewer:${this.viewerID}`;
39
39
  }
40
40
  }
41
41
  exports.IDViewer = IDViewer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.0-alpha73",
3
+ "version": "0.1.0-alpha76",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -4,14 +4,26 @@ export interface JSONOptions extends FieldOptions {
4
4
  validator?: (val: any) => boolean;
5
5
  importType?: ImportType;
6
6
  }
7
+ interface allJSONOptions extends JSONOptions {
8
+ jsonAsList?: boolean;
9
+ }
7
10
  export declare class JSONField extends BaseField implements Field {
8
11
  private options?;
9
12
  type: Type;
10
- constructor(jsonb: boolean, options?: JSONOptions | undefined);
13
+ constructor(jsonb: boolean, options?: allJSONOptions | undefined);
11
14
  format(val: any): string;
12
15
  valid(val: any): boolean;
13
16
  }
14
17
  export declare function JSONType(options?: JSONOptions): JSONField;
15
18
  export declare function JSONBType(options?: JSONOptions): JSONField;
19
+ /**
20
+ * @deprecated use JSONBTypeAsList
21
+ */
16
22
  export declare function JSONBListType(options?: JSONOptions): ListField;
23
+ /**
24
+ * @deprecated use JSONTypeAsList
25
+ */
17
26
  export declare function JSONListType(options?: JSONOptions): ListField;
27
+ export declare function JSONBTypeAsList(options?: JSONOptions): JSONField & JSONOptions;
28
+ export declare function JSONTypeAsList(options?: JSONOptions): JSONField & JSONOptions;
29
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.JSONListType = exports.JSONBListType = exports.JSONBType = exports.JSONType = exports.JSONField = void 0;
3
+ exports.JSONTypeAsList = exports.JSONBTypeAsList = exports.JSONListType = exports.JSONBListType = exports.JSONBType = exports.JSONType = exports.JSONField = void 0;
4
4
  const schema_1 = require("./schema");
5
5
  const field_1 = require("./field");
6
6
  class JSONField extends field_1.BaseField {
@@ -16,6 +16,11 @@ class JSONField extends field_1.BaseField {
16
16
  if (options?.importType) {
17
17
  this.type.importType = options.importType;
18
18
  }
19
+ if (options?.jsonAsList) {
20
+ this.type.listElemType = {
21
+ dbType: schema_1.DBType.JSONB,
22
+ };
23
+ }
19
24
  }
20
25
  format(val) {
21
26
  return JSON.stringify(val);
@@ -38,11 +43,33 @@ function JSONBType(options) {
38
43
  return Object.assign(result, options);
39
44
  }
40
45
  exports.JSONBType = JSONBType;
46
+ /**
47
+ * @deprecated use JSONBTypeAsList
48
+ */
41
49
  function JSONBListType(options) {
42
50
  return new field_1.ListField(JSONBType(options), options);
43
51
  }
44
52
  exports.JSONBListType = JSONBListType;
53
+ /**
54
+ * @deprecated use JSONTypeAsList
55
+ */
45
56
  function JSONListType(options) {
46
57
  return new field_1.ListField(JSONType(options), options);
47
58
  }
48
59
  exports.JSONListType = JSONListType;
60
+ function JSONBTypeAsList(options) {
61
+ let result = new JSONField(true, {
62
+ ...options,
63
+ jsonAsList: true,
64
+ });
65
+ return Object.assign(result, options);
66
+ }
67
+ exports.JSONBTypeAsList = JSONBTypeAsList;
68
+ function JSONTypeAsList(options) {
69
+ let result = new JSONField(false, {
70
+ ...options,
71
+ jsonAsList: true,
72
+ });
73
+ return Object.assign(result, options);
74
+ }
75
+ exports.JSONTypeAsList = JSONTypeAsList;
@@ -205,6 +205,7 @@ export interface FieldOptions {
205
205
  privacyPolicy?: PrivacyPolicy | (() => PrivacyPolicy);
206
206
  getDerivedFields?(name: string): FieldMap;
207
207
  convert?: ConvertType;
208
+ fetchOnDemand?: boolean;
208
209
  [x: string]: any;
209
210
  }
210
211
  export interface PolymorphicOptions {
@@ -6,12 +6,22 @@ export interface StructOptions extends FieldOptions {
6
6
  graphQLType?: string;
7
7
  jsonNotJSONB?: boolean;
8
8
  }
9
+ interface allStructOptions extends StructOptions {
10
+ jsonAsList?: boolean;
11
+ }
9
12
  export declare class StructField extends BaseField implements Field {
10
13
  private options;
11
14
  type: Type;
12
- constructor(options: StructOptions);
15
+ constructor(options: allStructOptions);
16
+ formatImpl(obj: any, nested?: boolean): string | Object;
13
17
  format(obj: any, nested?: boolean): string | Object;
18
+ private validImpl;
14
19
  valid(obj: any): Promise<boolean>;
15
20
  }
16
21
  export declare function StructType(options: StructOptions): StructField & StructOptions;
22
+ /**
23
+ * @deprecated use StructTypeAsList
24
+ */
17
25
  export declare function StructListType(options: StructOptions): ListField;
26
+ export declare function StructTypeAsList(options: allStructOptions): StructField & allStructOptions;
27
+ export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StructListType = exports.StructType = exports.StructField = void 0;
3
+ exports.StructTypeAsList = exports.StructListType = exports.StructType = exports.StructField = void 0;
4
4
  const field_1 = require("./field");
5
5
  const schema_1 = require("./schema");
6
6
  const camel_case_1 = require("camel-case");
@@ -17,11 +17,16 @@ class StructField extends field_1.BaseField {
17
17
  if (options.jsonNotJSONB) {
18
18
  this.type.dbType = schema_1.DBType.JSON;
19
19
  }
20
+ if (options?.jsonAsList) {
21
+ this.type.listElemType = {
22
+ dbType: schema_1.DBType.JSONB,
23
+ };
24
+ }
20
25
  }
21
26
  // right now, we store things in the db in lowerCase format
22
27
  // this will lead to issues if field changes.
23
28
  // TODO: use storageKey and convert back...
24
- format(obj, nested) {
29
+ formatImpl(obj, nested) {
25
30
  if (!(obj instanceof Object)) {
26
31
  throw new Error("valid was not called");
27
32
  }
@@ -47,13 +52,23 @@ class StructField extends field_1.BaseField {
47
52
  ret[dbKey] = val;
48
53
  }
49
54
  }
50
- // don't json.stringify if nested
55
+ // don't json.stringify if nested or list
51
56
  if (nested) {
52
57
  return ret;
53
58
  }
54
59
  return JSON.stringify(ret);
55
60
  }
56
- async valid(obj) {
61
+ format(obj, nested) {
62
+ if (Array.isArray(obj) && this.options.jsonAsList) {
63
+ const ret = obj.map((v) => this.formatImpl(v, true));
64
+ if (nested) {
65
+ return ret;
66
+ }
67
+ return JSON.stringify(ret);
68
+ }
69
+ return this.formatImpl(obj, nested);
70
+ }
71
+ async validImpl(obj) {
57
72
  if (!(obj instanceof Object)) {
58
73
  return false;
59
74
  }
@@ -89,6 +104,19 @@ class StructField extends field_1.BaseField {
89
104
  const ret = await Promise.all(promises);
90
105
  return ret.every((v) => v);
91
106
  }
107
+ async valid(obj) {
108
+ if (this.options.jsonAsList) {
109
+ if (!Array.isArray(obj)) {
110
+ return false;
111
+ }
112
+ const valid = await Promise.all(obj.map((v) => this.validImpl(v)));
113
+ return valid.every((b) => b);
114
+ }
115
+ if (!(obj instanceof Object)) {
116
+ return false;
117
+ }
118
+ return this.validImpl(obj);
119
+ }
92
120
  }
93
121
  exports.StructField = StructField;
94
122
  function StructType(options) {
@@ -96,7 +124,18 @@ function StructType(options) {
96
124
  return Object.assign(result, options);
97
125
  }
98
126
  exports.StructType = StructType;
127
+ /**
128
+ * @deprecated use StructTypeAsList
129
+ */
99
130
  function StructListType(options) {
100
131
  return new field_1.ListField(StructType(options), options);
101
132
  }
102
133
  exports.StructListType = StructListType;
134
+ function StructTypeAsList(options) {
135
+ let result = new StructField({
136
+ ...options,
137
+ jsonAsList: true,
138
+ });
139
+ return Object.assign(result, options);
140
+ }
141
+ exports.StructTypeAsList = StructTypeAsList;