jinaga-server 1.1.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +12 -0
  2. package/dist/authentication/authentication-device.d.ts +3 -2
  3. package/dist/authentication/authentication-device.js +6 -0
  4. package/dist/authentication/authentication-device.js.map +1 -1
  5. package/dist/authentication/authentication-impl.d.ts +3 -2
  6. package/dist/authentication/authentication-impl.js +6 -0
  7. package/dist/authentication/authentication-impl.js.map +1 -1
  8. package/dist/authentication/authentication-noop.d.ts +20 -0
  9. package/dist/authentication/authentication-noop.js +52 -0
  10. package/dist/authentication/authentication-noop.js.map +1 -0
  11. package/dist/authentication/authentication-session.d.ts +3 -2
  12. package/dist/authentication/authentication-session.js +6 -0
  13. package/dist/authentication/authentication-session.js.map +1 -1
  14. package/dist/authentication.d.ts +3 -2
  15. package/dist/authentication.js +6 -0
  16. package/dist/authentication.js.map +1 -1
  17. package/dist/authorization/authorization-keystore.d.ts +3 -4
  18. package/dist/authorization/authorization-keystore.js +9 -4
  19. package/dist/authorization/authorization-keystore.js.map +1 -1
  20. package/dist/debug.d.ts +1 -0
  21. package/dist/debug.js +30 -0
  22. package/dist/debug.js.map +1 -0
  23. package/dist/http/router.js.map +1 -1
  24. package/dist/jinaga-server.js +15 -7
  25. package/dist/jinaga-server.js.map +1 -1
  26. package/dist/memory/memory-keystore.js +0 -6
  27. package/dist/memory/memory-keystore.js.map +1 -1
  28. package/dist/postgres/connection.js +1 -0
  29. package/dist/postgres/connection.js.map +1 -1
  30. package/dist/postgres/maps.d.ts +7 -6
  31. package/dist/postgres/maps.js +9 -1
  32. package/dist/postgres/maps.js.map +1 -1
  33. package/dist/postgres/postgres-keystore.js +0 -6
  34. package/dist/postgres/postgres-keystore.js.map +1 -1
  35. package/dist/postgres/postgres-store.d.ts +1 -2
  36. package/dist/postgres/postgres-store.js +29 -24
  37. package/dist/postgres/postgres-store.js.map +1 -1
  38. package/dist/postgres/specification-result-sql.d.ts +6 -4
  39. package/dist/postgres/specification-result-sql.js +63 -23
  40. package/dist/postgres/specification-result-sql.js.map +1 -1
  41. package/dist/postgres/specification-sql.js.map +1 -1
  42. package/dist/postgres/sql.d.ts +1 -1
  43. package/dist/postgres/sql.js +15 -7
  44. package/dist/postgres/sql.js.map +1 -1
  45. package/dist/util/maps.d.ts +0 -0
  46. package/dist/util/maps.js +2 -0
  47. package/dist/util/maps.js.map +1 -0
  48. package/docs/sql/SQL Generator (projection).png +0 -0
  49. package/docs/sql/SQLFromSpecification.md +2 -0
  50. package/integration-test/jinaga-test/Dockerfile +2 -2
  51. package/integration-test/jinaga-test/jinaga.test.js +3 -3
  52. package/integration-test/jinaga-test/package-lock.json +745 -3484
  53. package/integration-test/jinaga-test/package.json +2 -0
  54. package/integration-test/run.sh +3 -3
  55. package/package.json +5 -5
  56. package/replicator/index.ts +31 -0
  57. package/replicator/package-lock.json +855 -0
  58. package/replicator/package.json +27 -0
  59. package/replicator/tsconfig.json +15 -0
  60. package/src/authentication/authentication-device.ts +11 -1
  61. package/src/authentication/authentication-impl.ts +9 -2
  62. package/src/authentication/authentication-noop.ts +65 -0
  63. package/src/authentication/authentication-session.ts +11 -1
  64. package/src/authentication.ts +12 -2
  65. package/src/authorization/authorization-keystore.ts +12 -7
  66. package/src/debug.ts +30 -0
  67. package/src/http/router.ts +2 -2
  68. package/src/jinaga-server.ts +18 -8
  69. package/src/memory/memory-keystore.ts +0 -6
  70. package/src/postgres/connection.ts +2 -1
  71. package/src/postgres/maps.ts +8 -0
  72. package/src/postgres/postgres-keystore.ts +2 -8
  73. package/src/postgres/postgres-store.ts +37 -30
  74. package/src/postgres/specification-result-sql.ts +88 -43
  75. package/src/postgres/specification-sql.ts +1 -1
  76. package/src/postgres/sql.ts +23 -12
  77. package/src/util/maps.ts +0 -0
  78. package/test/postgres/postgresReadSpec.ts +4 -1
  79. package/test/postgres/postgresSpec.ts +31 -31
  80. package/test/postgres/postgresSpecificationSpec.ts +1 -1
  81. package/tsconfig.json +1 -0
  82. package/tsconfig.test.json +26 -0
  83. package/docs/specification.txt +0 -41
  84. package/interactive/index.js +0 -56
@@ -3,6 +3,7 @@ import {
3
3
  Direction,
4
4
  ExistentialCondition,
5
5
  FactEnvelope,
6
+ FactFeed,
6
7
  FactPath,
7
8
  FactRecord,
8
9
  FactReference,
@@ -20,9 +21,8 @@ import {
20
21
  Specification,
21
22
  Step,
22
23
  Storage,
23
- TopologicalSorter,
24
+ TopologicalSorter
24
25
  } from "jinaga";
25
- import { FactFeed } from "jinaga/dist/storage";
26
26
  import { PoolClient } from "pg";
27
27
 
28
28
  import { distinct, flatten } from "../util/fn";
@@ -37,6 +37,7 @@ import {
37
37
  emptyFactTypeMap,
38
38
  emptyPublicKeyMap,
39
39
  emptyRoleMap,
40
+ ensureGetFactTypeId,
40
41
  FactMap,
41
42
  FactTypeMap,
42
43
  getFactId,
@@ -48,7 +49,7 @@ import {
48
49
  mergeFactTypes,
49
50
  mergeRoleMaps,
50
51
  PublicKeyMap,
51
- RoleMap,
52
+ RoleMap
52
53
  } from "./maps";
53
54
  import { ResultSetTree, resultSqlFromSpecification, SqlQueryTree } from "./specification-result-sql";
54
55
  import { sqlFromFeed } from "./specification-sql";
@@ -167,7 +168,7 @@ export class PostgresStore implements Storage {
167
168
  } = await this.connectionFactory.withTransaction(async (connection) => {
168
169
  const factTypes = await storeFactTypes(facts, this.factTypeMap, connection);
169
170
  const existingFacts = await findExistingFacts(facts, factTypes, connection);
170
- const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, factTypes.get(f.type)));
171
+ const newFacts = facts.filter(f => !hasFact(existingFacts, f.hash, ensureGetFactTypeId(factTypes, f.type)));
171
172
  if (newFacts.length === 0) {
172
173
  return {
173
174
  newEnvelopes: [],
@@ -235,6 +236,11 @@ export class PostgresStore implements Storage {
235
236
  const factTypes = await this.loadFactTypesFromSpecification(specification);
236
237
  const roleMap = await this.loadRolesFromSpecification(specification, factTypes);
237
238
 
239
+ // If any of the start facts are not known types, the specification cannot be satisfied.
240
+ if (start.filter(f => getFactTypeId(factTypes, f.type) === undefined).length > 0) {
241
+ return [];
242
+ }
243
+
238
244
  const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
239
245
  if (composer === null) {
240
246
  return [];
@@ -317,7 +323,7 @@ export class PostgresStore implements Storage {
317
323
  private async loadRolesFromSteps(steps: Step[], factTypes: FactTypeMap, initialType: string) {
318
324
  const roleMap = this.roleMap;
319
325
  const unknownRoles = allRoles(steps, factTypes, getFactTypeId(factTypes, initialType))
320
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
326
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
321
327
  if (unknownRoles.length > 0) {
322
328
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
323
329
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -332,11 +338,12 @@ export class PostgresStore implements Storage {
332
338
  async loadRolesFromSpecification(specification: Specification, factTypes: FactTypeMap): Promise<RoleMap> {
333
339
  const roleMap = this.roleMap;
334
340
  const unknownRoles = getAllRoles(specification)
341
+ .filter(r => getFactTypeId(factTypes, r.successorType))
335
342
  .map(r => ({
336
- defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
343
+ successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
337
344
  role: r.name
338
345
  }))
339
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
346
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
340
347
  if (unknownRoles.length > 0) {
341
348
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
342
349
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -352,10 +359,10 @@ export class PostgresStore implements Storage {
352
359
  const roleMap = this.roleMap;
353
360
  const unknownRoles = getAllRolesFromFeed(feed)
354
361
  .map(r => ({
355
- defining_fact_type_id: getFactTypeId(factTypes, r.definingFactType),
362
+ successor_type_id: ensureGetFactTypeId(factTypes, r.successorType),
356
363
  role: r.name
357
364
  }))
358
- .filter(r => !hasRole(roleMap, r.defining_fact_type_id, r.role));
365
+ .filter(r => !hasRole(roleMap, r.successor_type_id, r.role));
359
366
  if (unknownRoles.length > 0) {
360
367
  const loadedRoles = await this.connectionFactory.with(async (connection) => {
361
368
  return await loadRoles(unknownRoles, roleMap, connection);
@@ -535,21 +542,21 @@ async function loadFactTypes(factTypeNames: string[], connection: PoolClient) {
535
542
  async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap: RoleMap, connection: PoolClient) {
536
543
  // Find distinct roles
537
544
  const roles = flatten(facts, fact => {
538
- const defining_fact_type_id = factTypes.get(fact.type);
545
+ const successor_type_id = ensureGetFactTypeId(factTypes, fact.type);
539
546
  return Object.keys(fact.predecessors).map(role => ({
540
547
  role,
541
- defining_fact_type_id
548
+ successor_type_id
542
549
  }));
543
550
  }).filter((role, index, array) => array.findIndex(r =>
544
551
  r.role === role.role &&
545
- r.defining_fact_type_id === role.defining_fact_type_id
552
+ r.successor_type_id === role.successor_type_id
546
553
  ) === index);
547
554
 
548
555
  if (roles.length > 0) {
549
556
  // Look up existing roles
550
557
  const roleIds = await loadRoles(roles, roleMap, connection);
551
558
  const remainingRoles = roles.filter(role => !hasRole(
552
- roleIds, role.defining_fact_type_id, role.role));
559
+ roleIds, role.successor_type_id, role.role));
553
560
  if (remainingRoles.length === 0) {
554
561
  return roleIds;
555
562
  }
@@ -562,7 +569,7 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
562
569
  ' RETURNING role_id, name, defining_fact_type_id;';
563
570
  const remainingRoleParameters = flatten(remainingRoles, (role) => [
564
571
  role.role,
565
- role.defining_fact_type_id
572
+ role.successor_type_id
566
573
  ]);
567
574
  const { rows: newRows }: RoleResult = await connection.query(insertSql, remainingRoleParameters);
568
575
  if (newRows.length !== remainingRoles.length) {
@@ -579,12 +586,12 @@ async function storeRoles(facts: FactRecord[], factTypes: FactTypeMap, roleMap:
579
586
  }
580
587
  }
581
588
 
582
- async function loadRoles(roles: { role: string; defining_fact_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
589
+ async function loadRoles(roles: { role: string; successor_type_id: number; }[], roleMap: RoleMap, connection: PoolClient) {
583
590
  const roleValues = roles.map((role, index) =>
584
591
  `($${index * 2 + 1}, $${index * 2 + 2}::integer)`);
585
592
  const roleParameters = flatten(roles, (role) => [
586
593
  role.role,
587
- role.defining_fact_type_id
594
+ role.successor_type_id
588
595
  ]);
589
596
 
590
597
  const lookUpSql = 'SELECT role.name, role.defining_fact_type_id, role.role_id' +
@@ -646,7 +653,7 @@ async function insertFactsEdgesAndAncestors(facts: FactRecord[], factTypes: Fact
646
653
 
647
654
  function sqlInsertFacts(facts: FactRecord[], roles: RoleMap, factTypes: FactTypeMap) {
648
655
  const factValues = facts.map((f, i) => `(\$${i * 3 + 1}, \$${i * 3 + 2}::integer, \$${i * 3 + 3}::jsonb)`);
649
- const factParameters = flatten(facts, (f) => [f.hash, factTypes.get(f.type), {
656
+ const factParameters = flatten(facts, (f) => [f.hash, ensureGetFactTypeId(factTypes, f.type), {
650
657
  fields: f.fields,
651
658
  predecessors: canonicalPredecessors(f.predecessors)
652
659
  }]);
@@ -669,8 +676,8 @@ function sqlInsertFactsEdgesAndAncestors(facts: FactRecord[], factParameters: (s
669
676
  let parameterOffset = factParameters.length;
670
677
  const edgeValues = edgeRecords.map((e, i) => `(\$${i * 5 + 1 + parameterOffset}, \$${i * 5 + 2 + parameterOffset}::integer, \$${i * 5 + 3 + parameterOffset}, \$${i * 5 + 4 + parameterOffset}::integer, \$${i * 5 + 5 + parameterOffset}::integer)`);
671
678
  const edgeParameters = flatten(edgeRecords, (e) => {
672
- const successor_fact_type_id = getFactTypeId(factTypes, e.successor_type);
673
- const predecessor_fact_type_id = getFactTypeId(factTypes, e.predecessor_type);
679
+ const successor_fact_type_id = ensureGetFactTypeId(factTypes, e.successor_type);
680
+ const predecessor_fact_type_id = ensureGetFactTypeId(factTypes, e.predecessor_type);
674
681
  return [
675
682
  e.successor_hash,
676
683
  successor_fact_type_id,
@@ -833,7 +840,7 @@ async function storePublicKeys(envelopes: FactEnvelope[], connection: PoolClient
833
840
 
834
841
  async function insertSignatures(envelopes: FactEnvelope[], allFacts: FactMap, factTypes: FactTypeMap, publicKey: PublicKeyMap, connection: PoolClient) {
835
842
  const signatureRecords = flatten(envelopes, envelope => envelope.signatures.map(signature => ({
836
- factId: getFactId(allFacts, envelope.fact.hash, getFactTypeId(factTypes, envelope.fact.type)),
843
+ factId: getFactId(allFacts, envelope.fact.hash, ensureGetFactTypeId(factTypes, envelope.fact.type)),
837
844
  publicKeyId: getPublicKeyId(publicKey, signature.publicKey),
838
845
  signature: signature.signature
839
846
  })));
@@ -862,35 +869,35 @@ function allFactTypes(steps: Step[]): string[] {
862
869
  return [...factTypes, ...childFactTypes].filter(distinct);
863
870
  }
864
871
 
865
- function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number) {
866
- let roles: { defining_fact_type_id: number; role: string }[] = [];
867
- let defining_fact_type_id = initialTypeId;
872
+ function allRoles(steps: Step[], factTypes: FactTypeMap, initialTypeId: number | undefined) {
873
+ let roles: { successor_type_id: number; role: string }[] = [];
874
+ let successor_type_id = initialTypeId;
868
875
  let role: string | undefined = undefined;
869
876
 
870
877
  for (const step of steps) {
871
878
  if (step instanceof PropertyCondition) {
872
879
  if (step.name === 'type') {
873
- defining_fact_type_id = getFactTypeId(factTypes, step.value);
874
- if (defining_fact_type_id && role) {
875
- roles.push({ defining_fact_type_id, role });
880
+ successor_type_id = getFactTypeId(factTypes, step.value);
881
+ if (successor_type_id && role) {
882
+ roles.push({ successor_type_id: successor_type_id, role });
876
883
  }
877
884
  role = undefined;
878
885
  }
879
886
  }
880
887
  else if (step instanceof Join) {
881
888
  if (step.direction === Direction.Predecessor) {
882
- if (defining_fact_type_id) {
883
- roles.push({ defining_fact_type_id, role: step.role });
889
+ if (successor_type_id) {
890
+ roles.push({ successor_type_id: successor_type_id, role: step.role });
884
891
  }
885
892
  role = undefined;
886
893
  }
887
894
  else {
888
895
  role = step.role;
889
896
  }
890
- defining_fact_type_id = undefined;
897
+ successor_type_id = undefined;
891
898
  }
892
899
  else if (step instanceof ExistentialCondition) {
893
- roles = roles.concat(allRoles(step.steps, factTypes, defining_fact_type_id));
900
+ roles = roles.concat(allRoles(step.steps, factTypes, successor_type_id));
894
901
  }
895
902
  }
896
903
 
@@ -1,17 +1,19 @@
1
1
  import {
2
- ChildProjections,
3
- ElementProjection,
2
+ ComponentProjection,
3
+ FactProjection,
4
4
  FactReference,
5
+ FieldProjection,
6
+ HashProjection,
5
7
  Label,
6
8
  Match,
7
9
  PathCondition,
8
- ResultProjection,
10
+ Projection,
9
11
  SingularProjection,
10
12
  Specification,
11
- SpecificationProjection,
13
+ SpecificationProjection
12
14
  } from "jinaga";
13
15
 
14
- import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
16
+ import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
15
17
 
16
18
  interface SpecificationLabel {
17
19
  name: string;
@@ -222,7 +224,7 @@ class QueryDescription {
222
224
  const firstEdge = this.edges[0];
223
225
  const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
224
226
  const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
225
- const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
227
+ const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
226
228
  const writtenFactIndexes = new Set<number>().add(firstFactIndex);
227
229
  const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
228
230
  const inputWhereClauses = this.inputs
@@ -410,13 +412,13 @@ function generateExistentialWhereClause(existentialCondition: ExistentialConditi
410
412
  return `SELECT 1 FROM public.edge e${firstEdge.edgeIndex}${joins.join("")} WHERE ${whereClause.join(" AND ")}${inputWhereClauses}${existentialWhereClauses}`;
411
413
  }
412
414
 
413
- type FactByIdentifier = {
414
- [identifier: string]: FactDescription;
415
+ type FactByLabel = {
416
+ [label: string]: FactDescription;
415
417
  };
416
418
 
417
419
  interface ResultDescription {
418
420
  queryDescription: QueryDescription;
419
- resultProjection: ResultProjection;
421
+ resultProjection: Projection;
420
422
  childResultDescriptions: NamedResultDescription[];
421
423
  }
422
424
 
@@ -455,7 +457,7 @@ interface NamedResultSetTree extends ResultSetTree {
455
457
  export class ResultComposer {
456
458
  constructor(
457
459
  private readonly sqlQuery: SpecificationSqlQuery,
458
- private readonly resultProjection: ResultProjection,
460
+ private readonly resultProjection: Projection,
459
461
  private readonly parentFactIdLength: number,
460
462
  private readonly childResultComposers: NamedResultComposer[]
461
463
  ) { }
@@ -555,40 +557,77 @@ export class ResultComposer {
555
557
  }
556
558
 
557
559
  private projectionOf(row: any): any {
558
- if (!Array.isArray(this.resultProjection)) {
559
- return this.singularValue(this.resultProjection, row);
560
+ if (this.resultProjection.type === "field") {
561
+ return this.fieldValue(this.resultProjection, row);
560
562
  }
561
- else if (this.resultProjection.length === 0 && this.childResultComposers.length === 0) {
562
- return this.sqlQuery.labels
563
- .slice(this.parentFactIdLength)
564
- .reduce((acc, label) => ({
563
+ else if (this.resultProjection.type === "composite") {
564
+ if (
565
+ this.resultProjection.components.length === 0 &&
566
+ this.childResultComposers.length === 0) {
567
+ return this.sqlQuery.labels
568
+ .slice(this.parentFactIdLength)
569
+ .reduce((acc, label) => ({
570
+ ...acc,
571
+ [label.name]: row[`data${label.index}`].fields
572
+ }), {})
573
+ }
574
+ else {
575
+ return this.resultProjection.components.reduce((acc, component) => ({
565
576
  ...acc,
566
- [label.name]: row[`data${label.index}`].fields
567
- }), {})
577
+ [component.name]: this.elementValue(component, row)
578
+ }), {});
579
+ }
580
+ }
581
+ else if (this.resultProjection.type === "hash") {
582
+ return this.hashValue(this.resultProjection, row);
583
+ }
584
+ else if (this.resultProjection.type === "fact") {
585
+ return this.factValue(this.resultProjection, row);
568
586
  }
569
587
  else {
570
- return this.resultProjection.reduce((acc, elementProjection) => ({
571
- ...acc,
572
- [elementProjection.name]: this.elementValue(elementProjection, row)
573
- }), {});
588
+ const _exhaustiveCheck: never = this.resultProjection;
589
+ throw new Error(`Unknown projection type ${(this.resultProjection as any).type}`);
574
590
  }
575
591
  }
576
592
 
577
- private elementValue(projection: ElementProjection, row: any): any {
578
- const label = this.getLabel(projection.label);
593
+ private elementValue(projection: ComponentProjection, row: any): any {
579
594
  if (projection.type === "field") {
595
+ const label = this.getLabel(projection.label);
580
596
  return row[`data${label.index}`].fields[projection.field];
581
597
  }
582
598
  else if (projection.type === "hash") {
599
+ const label = this.getLabel(projection.label);
583
600
  return row[`hash${label.index}`];
584
601
  }
602
+ else if (projection.type === "fact") {
603
+ const label = this.getLabel(projection.label);
604
+ return row[`data${label.index}`].fields;
605
+ }
606
+ else if (projection.type === "specification") {
607
+ // This should have already been taken care of
608
+ return null;
609
+ }
610
+ else {
611
+ const _exhaustiveCheck: never = projection;
612
+ throw new Error(`Unknown projection type ${(projection as any).type}`);
613
+ }
585
614
  }
586
615
 
587
- private singularValue(projection: SingularProjection, row: any): any {
616
+ private fieldValue(projection: FieldProjection, row: any): any {
588
617
  const label = this.getLabel(projection.label);
589
618
  return row[`data${label.index}`].fields[projection.field];
590
619
  }
591
620
 
621
+ private hashValue(projection: HashProjection, row: any): any {
622
+ const label = this.getLabel(projection.label);
623
+ return row[`hash${label.index}`];
624
+ }
625
+
626
+ private factValue(projection: FactProjection, row: any): any {
627
+ const label = this.getLabel(projection.label);
628
+ return row[`data${label.index}`].fields;
629
+ }
630
+
592
631
  private getLabel(name: string) {
593
632
  const label = this.sqlQuery.labels.find(label => label.name === name);
594
633
  if (!label) {
@@ -626,27 +665,30 @@ class ResultDescriptionBuilder {
626
665
  // The DescriptionBuilder will branch at various points, and
627
666
  // build on the current query description along each branch.
628
667
  const initialQueryDescription = new QueryDescription([], [], [], [], [], []);
629
- return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.childProjections, {}, []);
668
+ return this.createResultDescription(initialQueryDescription, specification.given, start, specification.matches, specification.projection, {}, []);
630
669
  }
631
670
 
632
- private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], childProjections: ChildProjections, knownFacts: FactByIdentifier, path: number[]): ResultDescription {
671
+ private createResultDescription(queryDescription: QueryDescription, given: Label[], start: FactReference[], matches: Match[], projection: Projection, knownFacts: FactByLabel, path: number[]): ResultDescription {
633
672
  ({ queryDescription, knownFacts } = this.addEdges(queryDescription, given, start, knownFacts, path, matches));
634
673
  if (!queryDescription.isSatisfiable()) {
635
674
  // Abort the branch if the query is not satisfiable
636
675
  return {
637
676
  queryDescription,
638
- resultProjection: [],
677
+ resultProjection: {
678
+ type: "composite",
679
+ components: []
680
+ },
639
681
  childResultDescriptions: []
640
682
  }
641
683
  }
642
684
  const childResultDescriptions: NamedResultDescription[] = [];
643
- if (Array.isArray(childProjections)) {
644
- const specificationProjections = childProjections
645
- .filter(projection => projection.type === "specification") as SpecificationProjection[];
646
- const elementProjections = childProjections
647
- .filter(projection => projection.type === "field" || projection.type === "hash") as ElementProjection[];
685
+ if (projection.type === "composite") {
686
+ const specificationProjections = projection.components
687
+ .filter(projection => projection.type === "specification") as ({ name: string } & SpecificationProjection)[];
688
+ const singularProjections = projection.components
689
+ .filter(projection => projection.type === "field" || projection.type === "hash" || projection.type === "fact") as ({ name: string } & SingularProjection)[];
648
690
  for (const child of specificationProjections) {
649
- const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.childProjections, knownFacts, []);
691
+ const childResultDescription = this.createResultDescription(queryDescription, given, start, child.matches, child.projection, knownFacts, []);
650
692
  childResultDescriptions.push({
651
693
  name: child.name,
652
694
  ...childResultDescription
@@ -654,20 +696,23 @@ class ResultDescriptionBuilder {
654
696
  }
655
697
  return {
656
698
  queryDescription,
657
- resultProjection: elementProjections,
699
+ resultProjection: {
700
+ type: "composite",
701
+ components: singularProjections
702
+ },
658
703
  childResultDescriptions
659
704
  };
660
705
  }
661
706
  else {
662
707
  return {
663
708
  queryDescription,
664
- resultProjection: childProjections,
709
+ resultProjection: projection,
665
710
  childResultDescriptions: []
666
711
  }
667
712
  }
668
713
  }
669
714
 
670
- private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
715
+ private addEdges(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], matches: Match[]): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
671
716
  for (const match of matches) {
672
717
  for (const condition of match.conditions) {
673
718
  if (condition.type === "path") {
@@ -701,7 +746,7 @@ class ResultDescriptionBuilder {
701
746
  };
702
747
  }
703
748
 
704
- private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByIdentifier, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByIdentifier } {
749
+ private addPathCondition(queryDescription: QueryDescription, given: Label[], start: FactReference[], knownFacts: FactByLabel, path: number[], unknown: Label, prefix: string, condition: PathCondition): { queryDescription: QueryDescription, knownFacts: FactByLabel } {
705
750
  // If no input parameter has been allocated, allocate one now.
706
751
  if (!knownFacts.hasOwnProperty(condition.labelRight)) {
707
752
  const givenIndex = given.findIndex(given => given.name === condition.labelRight);
@@ -710,7 +755,7 @@ class ResultDescriptionBuilder {
710
755
  }
711
756
  const { queryDescription: newQueryDescription, factDescription } = queryDescription.withInputParameter(
712
757
  given[givenIndex],
713
- getFactTypeId(this.factTypes, start[givenIndex].type),
758
+ ensureGetFactTypeId(this.factTypes, start[givenIndex].type),
714
759
  start[givenIndex].hash,
715
760
  path
716
761
  );
@@ -753,11 +798,11 @@ class ResultDescriptionBuilder {
753
798
  }
754
799
  else {
755
800
  // If we have not written the fact, we need to write it now.
756
- const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.targetType);
801
+ const { query, factIndex: predecessorFactIndex } = queryWithParameter.withFact(role.predecessorType);
757
802
  queryDescription = query.withEdge(predecessorFactIndex, factIndex, roleParameter, path);
758
803
  factIndex = predecessorFactIndex;
759
804
  }
760
- type = role.targetType;
805
+ type = role.predecessorType;
761
806
  }
762
807
 
763
808
  const rightType = type;
@@ -785,7 +830,7 @@ class ResultDescriptionBuilder {
785
830
  roleId,
786
831
  declaringType: type
787
832
  });
788
- type = role.targetType;
833
+ type = role.predecessorType;
789
834
  }
790
835
 
791
836
  if (type !== rightType) {
@@ -823,7 +868,7 @@ function idsEqual(a: number[], b: number[]) {
823
868
  return a.every((value, index) => value === b[index]);
824
869
  }
825
870
 
826
- export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer {
871
+ export function resultSqlFromSpecification(start: FactReference[], specification: Specification, factTypes: FactTypeMap, roleMap: RoleMap): ResultComposer | null {
827
872
  const descriptionBuilder = new ResultDescriptionBuilder(factTypes, roleMap);
828
873
  const description = descriptionBuilder.buildDescription(start, specification);
829
874
 
@@ -63,7 +63,7 @@ class QueryDescription {
63
63
  const firstEdge = this.edges[0];
64
64
  const predecessorFact = this.inputs.find(i => i.factIndex === firstEdge.predecessorFactIndex);
65
65
  const successorFact = this.inputs.find(i => i.factIndex === firstEdge.successorFactIndex);
66
- const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact.factIndex;
66
+ const firstFactIndex = predecessorFact ? predecessorFact.factIndex : successorFact!.factIndex;
67
67
  const writtenFactIndexes = new Set<number>().add(firstFactIndex);
68
68
  const joins: string[] = generateJoins(this.edges, writtenFactIndexes);
69
69
  const inputWhereClauses = this.inputs
@@ -1,6 +1,6 @@
1
1
  import { Direction, ExistentialCondition, FactReference, Join, PropertyCondition, Quantifier, Step } from "jinaga";
2
2
 
3
- import { FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
3
+ import { ensureGetFactTypeId, FactTypeMap, getFactTypeId, getRoleId, RoleMap } from "./maps";
4
4
 
5
5
  export type SqlQuery = {
6
6
  empty: boolean,
@@ -10,7 +10,7 @@ export type SqlQuery = {
10
10
  factTypeNames: string[]
11
11
  };
12
12
 
13
- export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery {
13
+ export function sqlFromSteps(start: FactReference, steps: Step[], factTypes: FactTypeMap, roleMap: RoleMap) : SqlQuery | null {
14
14
  const builder = new QueryBuilder(factTypes, roleMap);
15
15
 
16
16
  return builder.buildQuery(start, steps);
@@ -90,18 +90,18 @@ class QueryBuilder {
90
90
  constructor(private factTypes: FactTypeMap, private roleMap: RoleMap) {
91
91
  }
92
92
 
93
- buildQuery(start: FactReference, steps: Step[]): SqlQuery {
93
+ buildQuery(start: FactReference, steps: Step[]): SqlQuery | null {
94
94
  if (steps.length === 0) {
95
95
  return null;
96
96
  }
97
97
 
98
- const startTypeId = getFactTypeId(this.factTypes, start.type);
98
+ const startTypeId = ensureGetFactTypeId(this.factTypes, start.type);
99
99
  const startState: QueryBuilderState = {
100
100
  state: 'start',
101
101
  typeId: startTypeId,
102
102
  typeName: start.type
103
103
  };
104
- const finalState = steps.reduce((state, step) => {
104
+ const finalState = steps.reduce((state: QueryBuilderState, step) => {
105
105
  return this.matchStep(state, step);
106
106
  }, startState);
107
107
  this.end(finalState);
@@ -134,6 +134,14 @@ class QueryBuilder {
134
134
  }
135
135
 
136
136
  private buildJoins(joins: QueryJoin[], priorFactId: string) {
137
+ interface Joins {
138
+ priorFactId: string,
139
+ clauses: string[]
140
+ };
141
+ const initialJoins: Joins = {
142
+ priorFactId,
143
+ clauses: []
144
+ };
137
145
  const clauses = joins.reduce((joins, join) => {
138
146
  if (join.table === 'edge') {
139
147
  if (join.direction === 'successor') {
@@ -161,10 +169,7 @@ class QueryBuilder {
161
169
  clauses: [...joins.clauses, clause]
162
170
  };
163
171
  }
164
- }, {
165
- priorFactId,
166
- clauses: []
167
- }).clauses;
172
+ }, initialJoins).clauses;
168
173
  return clauses.join('');
169
174
  }
170
175
 
@@ -209,7 +214,7 @@ class QueryBuilder {
209
214
  typeId: typeId,
210
215
  typeName: typeName
211
216
  };
212
- const finalState = steps.reduce((state, step) => {
217
+ const finalState = steps.reduce((state: QueryBuilderState, step) => {
213
218
  return this.matchStep(state, step);
214
219
  }, startState);
215
220
  this.end(finalState);
@@ -270,12 +275,15 @@ class QueryBuilder {
270
275
  position: `${state.typeName}.${step.role}`
271
276
  }
272
277
  }
273
- if (step.direction === Direction.Successor) {
278
+ else if (step.direction === Direction.Successor) {
274
279
  return {
275
280
  state: 'successor-join',
276
281
  role: step.role
277
282
  }
278
283
  }
284
+ else {
285
+ throw new Error(`Unknown direction ${step.direction}`);
286
+ }
279
287
  }
280
288
  else if (step instanceof ExistentialCondition) {
281
289
  return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
@@ -311,13 +319,16 @@ class QueryBuilder {
311
319
  position: `${state.typeName}.${step.role}`
312
320
  }
313
321
  }
314
- if (step.direction === Direction.Successor) {
322
+ else if (step.direction === Direction.Successor) {
315
323
  this.emitFact(state.typeName);
316
324
  return {
317
325
  state: 'successor-join',
318
326
  role: step.role
319
327
  }
320
328
  }
329
+ else {
330
+ throw new Error(`Unknown direction ${step.direction}`);
331
+ }
321
332
  }
322
333
  else if (step instanceof ExistentialCondition) {
323
334
  return this.pushExistentialCondition(state.typeId, state.typeName, state, step);
File without changes
@@ -27,7 +27,7 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
27
27
  // Filter out the roles named "unknown", and those of unknown fact types.
28
28
  let roleMap = getAllRoles(specification).filter(r => r.name !== 'unknown').reduce(
29
29
  (r, role, i) => {
30
- const factTypeId = getFactTypeId(factTypes, role.definingFactType);
30
+ const factTypeId = getFactTypeId(factTypes, role.successorType);
31
31
  if (!factTypeId) {
32
32
  return r;
33
33
  }
@@ -44,6 +44,9 @@ function sqlFor(descriptiveString: string, bookmarks: string[] = []) {
44
44
  throw new Error(`Unknown input type ${input.type}`);
45
45
  });
46
46
  const composer = resultSqlFromSpecification(start, specification, factTypes, roleMap);
47
+ if (!composer) {
48
+ throw new Error("The specification is not satisfiable.");
49
+ }
47
50
  return { composer, factTypes, roleMap };
48
51
  }
49
52