@snowtop/ent 0.1.0-alpha31 → 0.1.0-alpha40

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.
@@ -49,11 +49,11 @@ export interface Action<TEnt extends Ent<TViewer>, TBuilder extends Builder<TEnt
49
49
  changeset(): Promise<Changeset>;
50
50
  builder: TBuilder;
51
51
  getPrivacyPolicy(): PrivacyPolicy<TEnt>;
52
- getTriggers?(): Trigger<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[];
52
+ getTriggers?(): (Trigger<TEnt, TBuilder, TViewer, TInput, TExistingEnt> | Trigger<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[])[];
53
53
  getObservers?(): Observer<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[];
54
54
  getValidators?(): Validator<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[];
55
55
  getInput(): TInput;
56
- transformWrite?: (stmt: UpdateOperation<TEnt>) => Promise<TransformedUpdateOperation<TEnt>> | TransformedUpdateOperation<TEnt> | null;
56
+ transformWrite?: (stmt: UpdateOperation<TEnt, TViewer>) => Promise<TransformedUpdateOperation<TEnt>> | TransformedUpdateOperation<TEnt> | null;
57
57
  valid(): Promise<boolean>;
58
58
  validX(): Promise<void>;
59
59
  viewerForEntLoad?(data: Data): TViewer | Promise<TViewer>;
@@ -333,22 +333,46 @@ class Orchestrator {
333
333
  ]);
334
334
  }
335
335
  async triggers(action, builder, triggers) {
336
- await Promise.all(triggers.map(async (trigger) => {
337
- let ret = await trigger.changeset(builder, action.getInput());
338
- if (Array.isArray(ret)) {
339
- ret = await Promise.all(ret);
340
- }
341
- if (Array.isArray(ret)) {
342
- for (const v of ret) {
343
- if (typeof v === "object") {
344
- this.changesets.push(v);
345
- }
336
+ let groups = [];
337
+ let lastArray = 0;
338
+ let prevWasArray = false;
339
+ for (let i = 0; i < triggers.length; i++) {
340
+ let t = triggers[i];
341
+ if (Array.isArray(t)) {
342
+ if (!prevWasArray) {
343
+ // @ts-ignore
344
+ groups.push(triggers.slice(lastArray, i));
346
345
  }
346
+ groups.push(t);
347
+ prevWasArray = true;
348
+ lastArray++;
347
349
  }
348
- else if (ret) {
349
- this.changesets.push(ret);
350
+ else {
351
+ if (i === triggers.length - 1) {
352
+ // @ts-ignore
353
+ groups.push(triggers.slice(lastArray, i + 1));
354
+ }
355
+ prevWasArray = false;
350
356
  }
351
- }));
357
+ }
358
+ for (const triggers of groups) {
359
+ await Promise.all(triggers.map(async (trigger) => {
360
+ let ret = await trigger.changeset(builder, action.getInput());
361
+ if (Array.isArray(ret)) {
362
+ ret = await Promise.all(ret);
363
+ }
364
+ if (Array.isArray(ret)) {
365
+ for (const v of ret) {
366
+ if (typeof v === "object") {
367
+ this.changesets.push(v);
368
+ }
369
+ }
370
+ }
371
+ else if (ret) {
372
+ this.changesets.push(ret);
373
+ }
374
+ }));
375
+ }
352
376
  }
353
377
  async validators(validators, action, builder) {
354
378
  let promises = [];
@@ -379,23 +403,29 @@ class Orchestrator {
379
403
  // if disable transformations set, don't do schema transform and just do the right thing
380
404
  // else apply schema tranformation if it exists
381
405
  let transformed = null;
406
+ const sqlOp = this.getSQLStatementOperation();
382
407
  if (action?.transformWrite) {
383
408
  transformed = await action.transformWrite({
384
- viewer: builder.viewer,
385
- op: this.getSQLStatementOperation(),
409
+ builder,
410
+ input,
411
+ op: sqlOp,
386
412
  data: editedFields,
387
- existingEnt: this.existingEnt,
388
413
  });
389
414
  }
390
415
  else if (!this.disableTransformations) {
391
416
  transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
392
- viewer: builder.viewer,
393
- op: this.getSQLStatementOperation(),
417
+ builder,
418
+ input,
419
+ op: sqlOp,
394
420
  data: editedFields,
395
- existingEnt: this.existingEnt,
396
421
  });
397
422
  }
398
423
  if (transformed) {
424
+ if (sqlOp === schema_1.SQLStatementOperation.Insert && sqlOp !== transformed.op) {
425
+ if (!transformed.existingEnt) {
426
+ throw new Error(`cannot transform an insert operation without providing an existing ent`);
427
+ }
428
+ }
399
429
  if (transformed.data) {
400
430
  updateInput = true;
401
431
  for (const k in transformed.data) {
@@ -418,6 +448,8 @@ class Orchestrator {
418
448
  if (transformed.existingEnt) {
419
449
  // @ts-ignore
420
450
  this.existingEnt = transformed.existingEnt;
451
+ // modify existing ent in builder. it's readonly in generated ents but doesn't apply here
452
+ builder.existingEnt = transformed.existingEnt;
421
453
  }
422
454
  }
423
455
  // transforming before doing default fields so that we don't create a new id
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.0-alpha31",
3
+ "version": "0.1.0-alpha40",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -82,10 +82,10 @@ export declare enum SQLStatementOperation {
82
82
  Update = "update",
83
83
  Delete = "delete"
84
84
  }
85
- export interface UpdateOperation<T extends Ent> {
85
+ export interface UpdateOperation<TEnt extends Ent<TViewer>, TViewer extends Viewer = Viewer> {
86
86
  op: SQLStatementOperation;
87
- existingEnt: T | null;
88
- viewer: Viewer;
87
+ builder: Builder<TEnt, TViewer>;
88
+ input: Data;
89
89
  data?: Map<string, any>;
90
90
  }
91
91
  export interface TransformedUpdateOperation<T extends Ent> {
@@ -204,7 +204,7 @@ interface objectLoaderOptions {
204
204
  instanceKey?: string;
205
205
  }
206
206
  export declare function getObjectLoaderProperties(value: SchemaInputType, tableName: string): objectLoaderOptions | undefined;
207
- export declare function getTransformedUpdateOp<T extends Ent>(value: SchemaInputType, stmt: UpdateOperation<T>): TransformedUpdateOperation<T> | null;
207
+ export declare function getTransformedUpdateOp<TEnt extends Ent<TViewer>, TViewer extends Viewer>(value: SchemaInputType, stmt: UpdateOperation<TEnt, TViewer>): TransformedUpdateOperation<TEnt> | null;
208
208
  export declare enum ActionOperation {
209
209
  Create = 1,
210
210
  Edit = 2,
@@ -80,9 +80,12 @@ export declare class SimpleBuilder<T extends Ent, TExistingEnt extends TMaybleNu
80
80
  orchestrator: Orchestrator<T, Data, Viewer>;
81
81
  fields: Map<string, any>;
82
82
  nodeType: string;
83
+ m: Map<string, any>;
83
84
  constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation: WriteOperation, existingEnt: TExistingEnt, action?: Action<T, SimpleBuilder<T>, Viewer, Data> | undefined);
84
85
  getInput(): Data;
85
86
  updateInput(input: Data): void;
87
+ storeData(k: string, v: any): void;
88
+ getStoredData(k: string): any;
86
89
  build(): Promise<Changeset>;
87
90
  editedEnt(): Promise<T | null>;
88
91
  editedEntX(): Promise<T>;
@@ -100,7 +103,7 @@ export declare class SimpleAction<T extends Ent, TExistingEnt extends TMaybleNul
100
103
  builder: SimpleBuilder<T, TExistingEnt>;
101
104
  viewerForEntLoad: viewerEntLoadFunc | undefined;
102
105
  constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation: WriteOperation | undefined, existingEnt: TExistingEnt);
103
- getTriggers(): Trigger<T, SimpleBuilder<T>>[];
106
+ getTriggers(): (Trigger<T, SimpleBuilder<T>> | Array<Trigger<T, SimpleBuilder<T>>>)[];
104
107
  getValidators(): Validator<T, SimpleBuilder<T>>[];
105
108
  getObservers(): Observer<T, SimpleBuilder<T>>[];
106
109
  getPrivacyPolicy(): PrivacyPolicy<Ent<Viewer<Ent<any> | null, ID | null>>, Viewer<Ent<any> | null, ID | null>>;
@@ -150,6 +150,7 @@ class SimpleBuilder {
150
150
  this.schema = schema;
151
151
  this.operation = operation;
152
152
  this.existingEnt = existingEnt;
153
+ this.m = new Map();
153
154
  // create dynamic placeholder
154
155
  // TODO: do we need to use this as the node when there's an existingEnt
155
156
  // same for generated builders.
@@ -230,6 +231,14 @@ class SimpleBuilder {
230
231
  }
231
232
  }
232
233
  }
234
+ // store data in Builder that can be retrieved by another validator, trigger, observer later in the action
235
+ storeData(k, v) {
236
+ this.m.set(k, v);
237
+ }
238
+ // retrieve data stored in this Builder with key
239
+ getStoredData(k) {
240
+ return this.m.get(k);
241
+ }
233
242
  build() {
234
243
  return this.orchestrator.build();
235
244
  }
@@ -11,10 +11,12 @@ export declare class TransformAction implements TransformFile {
11
11
  rawString?: undefined;
12
12
  traversed?: undefined;
13
13
  imports?: undefined;
14
+ removeImports?: undefined;
14
15
  } | {
15
16
  rawString: string;
16
17
  traversed: boolean;
17
18
  imports: Map<string, string[]>;
19
+ removeImports: string[];
18
20
  node?: undefined;
19
21
  } | undefined;
20
22
  }
@@ -25,11 +25,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.TransformAction = void 0;
26
26
  const typescript_1 = __importDefault(require("typescript"));
27
27
  const ast_1 = require("../tsc/ast");
28
- const action_1 = require("../action");
29
28
  const viewer_1 = require("../core/viewer");
30
29
  const path = __importStar(require("path"));
31
30
  const snake_case_1 = require("snake-case");
32
- function findInput(file, classInfo, sourceFile) {
31
+ // returns input and importPath
32
+ function getBaseFileInfo(file, classInfo, sourceFile) {
33
33
  // @ts-ignore
34
34
  const importStatements = sourceFile.statements.filter((stmt) => typescript_1.default.isImportDeclaration(stmt));
35
35
  for (const imp of importStatements) {
@@ -49,13 +49,19 @@ function findInput(file, classInfo, sourceFile) {
49
49
  .filter((imp) => imp.trim() && imp.endsWith("Input"))
50
50
  .map((v) => v.trim());
51
51
  if (inputs.length === 1) {
52
- return inputs[0];
52
+ return {
53
+ input: inputs[0],
54
+ importPath: impInfo.importPath,
55
+ };
53
56
  }
54
57
  if (inputs.length && classInfo.name.endsWith("Action")) {
55
58
  const prefix = classInfo.name.slice(0, classInfo.name.length - 6);
56
59
  inputs = inputs.filter((imp) => imp.slice(0, imp.length - 5) === prefix);
57
60
  if (inputs.length === 1) {
58
- return inputs[0];
61
+ return {
62
+ input: inputs[0],
63
+ importPath: impInfo.importPath,
64
+ };
59
65
  }
60
66
  }
61
67
  }
@@ -66,17 +72,20 @@ let m = {
66
72
  triggers: {
67
73
  m: "getTriggers",
68
74
  i: "Trigger",
75
+ suffix: "Triggers",
69
76
  },
70
77
  observers: {
71
78
  m: "getObservers",
72
79
  i: "Observer",
80
+ suffix: "Observers",
73
81
  },
74
82
  validators: {
75
83
  m: "getValidators",
76
84
  i: "Validator",
85
+ suffix: "Validators",
77
86
  },
78
87
  };
79
- function getConversionInfo(mm) {
88
+ function getConversionInfo(mm, actionName) {
80
89
  if (mm.kind !== typescript_1.default.SyntaxKind.PropertyDeclaration) {
81
90
  return null;
82
91
  }
@@ -89,6 +98,8 @@ function getConversionInfo(mm) {
89
98
  text,
90
99
  method: v.m,
91
100
  interface: v.i,
101
+ // CreateFooActionTriggers etc
102
+ methodType: actionName + v.suffix,
92
103
  };
93
104
  }
94
105
  class TransformAction {
@@ -109,21 +120,19 @@ class TransformAction {
109
120
  // require action
110
121
  const p = require(path.join(process.cwd(), "./" + file.slice(0, -3)));
111
122
  const action = new p.default(new viewer_1.LoggedOutViewer(), {});
123
+ const actionName = action.constructor.name;
112
124
  const builder = action.builder.constructor.name;
113
125
  const nodeName = action.builder.ent.name;
114
- const existingEnt = action.builder.operation === action_1.WriteOperation.Insert
115
- ? `${nodeName} | null`
116
- : nodeName;
117
126
  const viewer = this.customInfo.viewerInfo.name;
118
- const input = findInput(file, classInfo, sourceFile);
119
- if (!input) {
127
+ const baseInfo = getBaseFileInfo(file, classInfo, sourceFile);
128
+ if (!baseInfo) {
120
129
  return;
121
130
  }
122
131
  let klassContents = "";
123
132
  let traversed = false;
124
133
  let newImports = [];
125
134
  for (const mm of node.members) {
126
- const conv = getConversionInfo(mm);
135
+ const conv = getConversionInfo(mm, actionName);
127
136
  if (conv !== null) {
128
137
  const property = mm;
129
138
  // if invalid, bounce
@@ -132,10 +141,10 @@ class TransformAction {
132
141
  }
133
142
  traversed = true;
134
143
  const pp = property.initializer.getFullText(sourceFile).trim();
135
- const code = `${conv.method}(): ${conv.interface}<${nodeName}, ${builder}<${input}, ${existingEnt}>, ${viewer}, ${input}, ${existingEnt}>[] {
144
+ const code = `${conv.method}(): ${conv.methodType} {
136
145
  return ${pp}
137
146
  }`;
138
- newImports.push(conv.interface);
147
+ newImports.push(conv.methodType);
139
148
  klassContents += (0, ast_1.getPreText)(contents, mm, sourceFile) + code;
140
149
  }
141
150
  else {
@@ -149,10 +158,9 @@ class TransformAction {
149
158
  [viewer],
150
159
  ],
151
160
  [
152
- (0, ast_1.transformRelative)(file, "src/ent", this.customInfo.relativeImports),
153
- [nodeName],
161
+ (0, ast_1.transformRelative)(file, baseInfo.importPath, this.customInfo.relativeImports),
162
+ newImports,
154
163
  ],
155
- ["@snowtop/ent/action", newImports],
156
164
  [
157
165
  (0, ast_1.transformRelative)(file, builderPath, this.customInfo.relativeImports),
158
166
  [builder],
@@ -163,6 +171,8 @@ class TransformAction {
163
171
  rawString: classInfo.wrapClassContents(klassContents),
164
172
  traversed,
165
173
  imports,
174
+ removeImports: ["Trigger", "Observer", "Validator"],
175
+ // not removing FooBuilder incase it's still somehow used in type of inline builders
166
176
  };
167
177
  }
168
178
  }
@@ -91,7 +91,7 @@ function getTransformClassInfo(fileContents, sourceFile, node, transformSchema)
91
91
  }
92
92
  // intentionally doesn't parse decorators since we don't need it
93
93
  function getClassElementInfo(fileContents, member, sourceFile) {
94
- if (isFieldElement(member, sourceFile)) {
94
+ if (isFieldElement(fileContents, member, sourceFile)) {
95
95
  return getFieldElementInfo(fileContents, member, sourceFile);
96
96
  }
97
97
  if (member.kind === typescript_1.default.SyntaxKind.Constructor) {
@@ -201,7 +201,7 @@ function getConstructorElementInfo(fileContents, member, sourceFile) {
201
201
  comment: "",
202
202
  };
203
203
  }
204
- function isFieldElement(member, sourceFile) {
204
+ function isFieldElement(fileContents, member, sourceFile) {
205
205
  if (member.kind !== typescript_1.default.SyntaxKind.PropertyDeclaration) {
206
206
  return false;
207
207
  }
@@ -215,16 +215,31 @@ function isFieldElement(member, sourceFile) {
215
215
  return false;
216
216
  }
217
217
  if (property.initializer?.kind !== typescript_1.default.SyntaxKind.ArrayLiteralExpression) {
218
- console.error("invalid array type");
218
+ throwErr(fileContents, member, "invalid array type");
219
219
  return false;
220
220
  }
221
221
  return true;
222
222
  }
223
+ // if there's an error transforming any of the schemas, we should stop...
224
+ function throwErr(fileContents, node, error) {
225
+ console.error(error);
226
+ throw new Error(`error transforming this field ${fileContents.substring(node.getFullStart(), node.getEnd())}`);
227
+ }
223
228
  function parseFieldElement(element, sourceFile, fileContents, nested) {
224
- if (element.kind !== typescript_1.default.SyntaxKind.CallExpression) {
225
- console.error("skipped non-call expression");
229
+ if (element.kind !== typescript_1.default.SyntaxKind.CallExpression &&
230
+ element.kind !== typescript_1.default.SyntaxKind.PropertyAccessExpression) {
231
+ throwErr(fileContents, element, `skipped unknown (non-call|non-property) expression ${element.kind}`);
226
232
  return null;
227
233
  }
234
+ if (element.kind === typescript_1.default.SyntaxKind.PropertyAccessExpression) {
235
+ const ret = parseFieldElement(element.expression, sourceFile, fileContents, true);
236
+ if (ret !== null) {
237
+ if (!nested) {
238
+ ret.suffix = fileContents.substring(ret.callEx.getEnd(), element.getEnd());
239
+ }
240
+ return ret;
241
+ }
242
+ }
228
243
  let callEx = element;
229
244
  if (callEx.arguments.length !== 1) {
230
245
  // have a situation like: StringType({ name: "canonicalName" }).trim().toLowerCase(),
@@ -238,12 +253,22 @@ function parseFieldElement(element, sourceFile, fileContents, nested) {
238
253
  return ret;
239
254
  }
240
255
  }
241
- console.error("callExpression with arguments not of length 1");
242
- return null;
256
+ throwErr(fileContents, element, "callExpression with arguments not of length 1");
243
257
  }
244
258
  let arg = callEx.arguments[0];
245
259
  if (arg.kind !== typescript_1.default.SyntaxKind.ObjectLiteralExpression) {
246
- console.error("not objectLiteralExpression");
260
+ // this and the check above for PropertyAccessExpression are to handle things like
261
+ // FooType({
262
+ /// ...
263
+ // }).function(blah)
264
+ const ret = parseFieldElement(callEx.expression, sourceFile, fileContents, true);
265
+ if (ret !== null) {
266
+ if (!nested) {
267
+ ret.suffix = fileContents.substring(ret.callEx.getEnd(), callEx.getEnd());
268
+ }
269
+ return ret;
270
+ }
271
+ throwErr(fileContents, element, `not objectLiteralExpression. kind ${arg.kind}`);
247
272
  return null;
248
273
  }
249
274
  let expr = arg;
@@ -263,7 +288,7 @@ function parseFieldElement(element, sourceFile, fileContents, nested) {
263
288
  }
264
289
  }
265
290
  if (!name) {
266
- console.error(`couldn't find name property`);
291
+ throwErr(fileContents, element, `couldn't find name property`);
267
292
  return null;
268
293
  }
269
294
  // remove quotes