@getstrata/core 0.5.98 → 0.5.100

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.
@@ -74,7 +74,7 @@ function buildOperatorClauses(column, operator, params) {
74
74
  clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
75
75
  }
76
76
  if (operator.ilike !== undefined) {
77
- clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
77
+ clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
78
78
  }
79
79
  if (operator.tsMatch !== undefined) {
80
80
  clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
@@ -24,26 +24,67 @@ function belongsToMany(definition) {
24
24
  ...definition
25
25
  };
26
26
  }
27
+ function relationMatchKey(value) {
28
+ if (value === null || value === undefined) {
29
+ return "";
30
+ }
31
+ if (typeof value === "bigint") {
32
+ return value.toString();
33
+ }
34
+ if (typeof value === "number" && Number.isFinite(value)) {
35
+ return String(value);
36
+ }
37
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
38
+ return BigInt(value).toString();
39
+ }
40
+ return String(value);
41
+ }
42
+ function getByRelationKey(map, key) {
43
+ if (map.has(key)) {
44
+ return map.get(key);
45
+ }
46
+ const want = relationMatchKey(key);
47
+ if (want === "") {
48
+ return;
49
+ }
50
+ for (const [existing, value] of map) {
51
+ if (relationMatchKey(existing) === want) {
52
+ return value;
53
+ }
54
+ }
55
+ return;
56
+ }
27
57
  function indexHasManyRelation(parents, children, relation) {
28
58
  const groups = new Map;
59
+ const originalKeys = new Map;
29
60
  for (const parent of parents) {
30
- groups.set(parent[relation.localKey], []);
61
+ const key = relationMatchKey(parent[relation.localKey]);
62
+ if (!groups.has(key)) {
63
+ groups.set(key, []);
64
+ originalKeys.set(key, parent[relation.localKey]);
65
+ }
31
66
  }
32
67
  for (const child of children) {
33
- const key = child[relation.foreignKey];
34
- const group = groups.get(key);
68
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
35
69
  if (!group) {
36
70
  continue;
37
71
  }
38
72
  group.push(child);
39
73
  }
40
- return groups;
74
+ const result = new Map;
75
+ for (const [key, group] of groups) {
76
+ const original = originalKeys.get(key);
77
+ if (original !== undefined) {
78
+ result.set(original, group);
79
+ }
80
+ }
81
+ return result;
41
82
  }
42
83
  function indexHasOneRelation(parents, children, relation) {
43
84
  const grouped = indexHasManyRelation(parents, children, relation);
44
85
  const result = new Map;
45
86
  for (const parent of parents) {
46
- const matches = grouped.get(parent[relation.localKey]) ?? [];
87
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
47
88
  result.set(parent[relation.localKey], matches[0]);
48
89
  }
49
90
  return result;
@@ -51,12 +92,12 @@ function indexHasOneRelation(parents, children, relation) {
51
92
  function indexBelongsToRelation(children, parents, relation) {
52
93
  const parentsById = new Map;
53
94
  for (const parent of parents) {
54
- parentsById.set(parent[relation.ownerKey], parent);
95
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
55
96
  }
56
97
  const result = new Map;
57
98
  for (const child of children) {
58
99
  const foreignKey = child[relation.foreignKey];
59
- const parent = parentsById.get(foreignKey);
100
+ const parent = parentsById.get(relationMatchKey(foreignKey));
60
101
  if (parent) {
61
102
  result.set(foreignKey, parent);
62
103
  }
@@ -66,23 +107,33 @@ function indexBelongsToRelation(children, parents, relation) {
66
107
  function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
67
108
  const relatedById = new Map;
68
109
  for (const related of relatedRows) {
69
- relatedById.set(related[relation.relatedKey], related);
110
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
70
111
  }
71
112
  const groups = new Map;
113
+ const originalKeys = new Map;
72
114
  for (const parent of parents) {
73
- groups.set(parent[relation.parentKey], []);
115
+ const key = relationMatchKey(parent[relation.parentKey]);
116
+ if (!groups.has(key)) {
117
+ groups.set(key, []);
118
+ originalKeys.set(key, parent[relation.parentKey]);
119
+ }
74
120
  }
75
121
  for (const pivot of pivotRows) {
76
- const parentId = pivot[relation.foreignPivotKey];
77
- const relatedId = pivot[relation.relatedPivotKey];
78
- const group = groups.get(parentId);
79
- const related = relatedById.get(relatedId);
122
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
123
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
80
124
  if (!group || !related) {
81
125
  continue;
82
126
  }
83
127
  group.push(related);
84
128
  }
85
- return groups;
129
+ const result = new Map;
130
+ for (const [key, group] of groups) {
131
+ const original = originalKeys.get(key);
132
+ if (original !== undefined) {
133
+ result.set(original, group);
134
+ }
135
+ }
136
+ return result;
86
137
  }
87
138
  function morphMany(definition) {
88
139
  return {
@@ -104,27 +155,38 @@ function morphTo(definition) {
104
155
  }
105
156
  function indexMorphManyRelation(parents, children, relation) {
106
157
  const groups = new Map;
158
+ const originalKeys = new Map;
107
159
  for (const parent of parents) {
108
- groups.set(parent[relation.localKey], []);
160
+ const key = relationMatchKey(parent[relation.localKey]);
161
+ if (!groups.has(key)) {
162
+ groups.set(key, []);
163
+ originalKeys.set(key, parent[relation.localKey]);
164
+ }
109
165
  }
110
166
  for (const child of children) {
111
167
  if (child[relation.morphTypeKey] !== relation.morphType) {
112
168
  continue;
113
169
  }
114
- const key = child[relation.morphIdKey];
115
- const group = groups.get(key);
170
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
116
171
  if (!group) {
117
172
  continue;
118
173
  }
119
174
  group.push(child);
120
175
  }
121
- return groups;
176
+ const result = new Map;
177
+ for (const [key, group] of groups) {
178
+ const original = originalKeys.get(key);
179
+ if (original !== undefined) {
180
+ result.set(original, group);
181
+ }
182
+ }
183
+ return result;
122
184
  }
123
185
  function indexMorphOneRelation(parents, children, relation) {
124
186
  const grouped = indexMorphManyRelation(parents, children, relation);
125
187
  const result = new Map;
126
188
  for (const parent of parents) {
127
- const matches = grouped.get(parent[relation.localKey]) ?? [];
189
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
128
190
  result.set(parent[relation.localKey], matches[0]);
129
191
  }
130
192
  return result;
@@ -137,7 +199,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
137
199
  if (!parents) {
138
200
  continue;
139
201
  }
140
- const parent = parents.get(child[relation.morphIdKey]);
202
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
141
203
  if (parent) {
142
204
  result.set(child[relation.morphIdKey], parent);
143
205
  }
@@ -147,6 +209,7 @@ function indexMorphToRelation(children, parentsByType, relation) {
147
209
  export {
148
210
  belongsTo,
149
211
  belongsToMany,
212
+ getByRelationKey,
150
213
  hasMany,
151
214
  hasOne,
152
215
  indexBelongsToManyRelation,
@@ -158,5 +221,6 @@ export {
158
221
  indexMorphToRelation,
159
222
  morphMany,
160
223
  morphOne,
161
- morphTo
224
+ morphTo,
225
+ relationMatchKey
162
226
  };
@@ -74,7 +74,7 @@ function buildOperatorClauses(column, operator, params) {
74
74
  clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
75
75
  }
76
76
  if (operator.ilike !== undefined) {
77
- clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
77
+ clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
78
78
  }
79
79
  if (operator.tsMatch !== undefined) {
80
80
  clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
@@ -422,6 +422,214 @@ function buildDeleteByIdQuery(table, id) {
422
422
  };
423
423
  }
424
424
 
425
+ // ../../src/core/database/relationships.ts
426
+ function hasMany(definition) {
427
+ return {
428
+ type: "hasMany",
429
+ ...definition
430
+ };
431
+ }
432
+ function hasOne(definition) {
433
+ return {
434
+ type: "hasOne",
435
+ ...definition
436
+ };
437
+ }
438
+ function belongsTo(definition) {
439
+ return {
440
+ type: "belongsTo",
441
+ ...definition
442
+ };
443
+ }
444
+ function belongsToMany(definition) {
445
+ return {
446
+ type: "belongsToMany",
447
+ ...definition
448
+ };
449
+ }
450
+ function relationMatchKey(value) {
451
+ if (value === null || value === undefined) {
452
+ return "";
453
+ }
454
+ if (typeof value === "bigint") {
455
+ return value.toString();
456
+ }
457
+ if (typeof value === "number" && Number.isFinite(value)) {
458
+ return String(value);
459
+ }
460
+ if (typeof value === "string" && /^-?\d+$/.test(value)) {
461
+ return BigInt(value).toString();
462
+ }
463
+ return String(value);
464
+ }
465
+ function getByRelationKey(map, key) {
466
+ if (map.has(key)) {
467
+ return map.get(key);
468
+ }
469
+ const want = relationMatchKey(key);
470
+ if (want === "") {
471
+ return;
472
+ }
473
+ for (const [existing, value] of map) {
474
+ if (relationMatchKey(existing) === want) {
475
+ return value;
476
+ }
477
+ }
478
+ return;
479
+ }
480
+ function indexHasManyRelation(parents, children, relation) {
481
+ const groups = new Map;
482
+ const originalKeys = new Map;
483
+ for (const parent of parents) {
484
+ const key = relationMatchKey(parent[relation.localKey]);
485
+ if (!groups.has(key)) {
486
+ groups.set(key, []);
487
+ originalKeys.set(key, parent[relation.localKey]);
488
+ }
489
+ }
490
+ for (const child of children) {
491
+ const group = groups.get(relationMatchKey(child[relation.foreignKey]));
492
+ if (!group) {
493
+ continue;
494
+ }
495
+ group.push(child);
496
+ }
497
+ const result = new Map;
498
+ for (const [key, group] of groups) {
499
+ const original = originalKeys.get(key);
500
+ if (original !== undefined) {
501
+ result.set(original, group);
502
+ }
503
+ }
504
+ return result;
505
+ }
506
+ function indexHasOneRelation(parents, children, relation) {
507
+ const grouped = indexHasManyRelation(parents, children, relation);
508
+ const result = new Map;
509
+ for (const parent of parents) {
510
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
511
+ result.set(parent[relation.localKey], matches[0]);
512
+ }
513
+ return result;
514
+ }
515
+ function indexBelongsToRelation(children, parents, relation) {
516
+ const parentsById = new Map;
517
+ for (const parent of parents) {
518
+ parentsById.set(relationMatchKey(parent[relation.ownerKey]), parent);
519
+ }
520
+ const result = new Map;
521
+ for (const child of children) {
522
+ const foreignKey = child[relation.foreignKey];
523
+ const parent = parentsById.get(relationMatchKey(foreignKey));
524
+ if (parent) {
525
+ result.set(foreignKey, parent);
526
+ }
527
+ }
528
+ return result;
529
+ }
530
+ function indexBelongsToManyRelation(parents, pivotRows, relatedRows, relation) {
531
+ const relatedById = new Map;
532
+ for (const related of relatedRows) {
533
+ relatedById.set(relationMatchKey(related[relation.relatedKey]), related);
534
+ }
535
+ const groups = new Map;
536
+ const originalKeys = new Map;
537
+ for (const parent of parents) {
538
+ const key = relationMatchKey(parent[relation.parentKey]);
539
+ if (!groups.has(key)) {
540
+ groups.set(key, []);
541
+ originalKeys.set(key, parent[relation.parentKey]);
542
+ }
543
+ }
544
+ for (const pivot of pivotRows) {
545
+ const group = groups.get(relationMatchKey(pivot[relation.foreignPivotKey]));
546
+ const related = relatedById.get(relationMatchKey(pivot[relation.relatedPivotKey]));
547
+ if (!group || !related) {
548
+ continue;
549
+ }
550
+ group.push(related);
551
+ }
552
+ const result = new Map;
553
+ for (const [key, group] of groups) {
554
+ const original = originalKeys.get(key);
555
+ if (original !== undefined) {
556
+ result.set(original, group);
557
+ }
558
+ }
559
+ return result;
560
+ }
561
+ function morphMany(definition) {
562
+ return {
563
+ type: "morphMany",
564
+ ...definition
565
+ };
566
+ }
567
+ function morphOne(definition) {
568
+ return {
569
+ type: "morphOne",
570
+ ...definition
571
+ };
572
+ }
573
+ function morphTo(definition) {
574
+ return {
575
+ type: "morphTo",
576
+ ...definition
577
+ };
578
+ }
579
+ function indexMorphManyRelation(parents, children, relation) {
580
+ const groups = new Map;
581
+ const originalKeys = new Map;
582
+ for (const parent of parents) {
583
+ const key = relationMatchKey(parent[relation.localKey]);
584
+ if (!groups.has(key)) {
585
+ groups.set(key, []);
586
+ originalKeys.set(key, parent[relation.localKey]);
587
+ }
588
+ }
589
+ for (const child of children) {
590
+ if (child[relation.morphTypeKey] !== relation.morphType) {
591
+ continue;
592
+ }
593
+ const group = groups.get(relationMatchKey(child[relation.morphIdKey]));
594
+ if (!group) {
595
+ continue;
596
+ }
597
+ group.push(child);
598
+ }
599
+ const result = new Map;
600
+ for (const [key, group] of groups) {
601
+ const original = originalKeys.get(key);
602
+ if (original !== undefined) {
603
+ result.set(original, group);
604
+ }
605
+ }
606
+ return result;
607
+ }
608
+ function indexMorphOneRelation(parents, children, relation) {
609
+ const grouped = indexMorphManyRelation(parents, children, relation);
610
+ const result = new Map;
611
+ for (const parent of parents) {
612
+ const matches = getByRelationKey(grouped, parent[relation.localKey]) ?? [];
613
+ result.set(parent[relation.localKey], matches[0]);
614
+ }
615
+ return result;
616
+ }
617
+ function indexMorphToRelation(children, parentsByType, relation) {
618
+ const result = new Map;
619
+ for (const child of children) {
620
+ const morphType = String(child[relation.morphTypeKey]);
621
+ const parents = parentsByType.get(morphType);
622
+ if (!parents) {
623
+ continue;
624
+ }
625
+ const parent = getByRelationKey(parents, child[relation.morphIdKey]);
626
+ if (parent) {
627
+ result.set(child[relation.morphIdKey], parent);
628
+ }
629
+ }
630
+ return result;
631
+ }
632
+
425
633
  // ../../src/core/database/whereBuilder.ts
426
634
  class WhereBuilder {
427
635
  nodes = [];
@@ -579,13 +787,30 @@ class RepositoryQuery {
579
787
  });
580
788
  return this;
581
789
  }
790
+ withBelongsToMany(as, relation, relatedRepository, options = {}) {
791
+ this.eagerLoads.push({
792
+ kind: "belongsToMany",
793
+ as,
794
+ relation,
795
+ repository: relatedRepository,
796
+ options
797
+ });
798
+ return this;
799
+ }
582
800
  async get() {
583
801
  const rows = await this.repository.findAll(this.buildOptions());
584
802
  return await this.attach(rows);
585
803
  }
586
804
  async first() {
587
- const rows = await this.get();
588
- return rows[0] ?? null;
805
+ const rows = await this.repository.findAll({ ...this.buildOptions(), limit: 1 });
806
+ const attached = await this.attach(rows);
807
+ return attached[0] ?? null;
808
+ }
809
+ async count() {
810
+ return await this.repository.count(this.buildOptions());
811
+ }
812
+ async attachToRows(rows) {
813
+ return await this.attach(rows);
589
814
  }
590
815
  async paginate(options) {
591
816
  return await this.repository.paginate({
@@ -635,7 +860,7 @@ class RepositoryQuery {
635
860
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
636
861
  result = result.map((row) => ({
637
862
  ...row,
638
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
863
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
639
864
  }));
640
865
  continue;
641
866
  }
@@ -644,7 +869,7 @@ class RepositoryQuery {
644
869
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
645
870
  result = result.map((row) => ({
646
871
  ...row,
647
- [load.as]: grouped2.get(row[relation2.localKey]) ?? []
872
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
648
873
  }));
649
874
  continue;
650
875
  }
@@ -653,7 +878,16 @@ class RepositoryQuery {
653
878
  const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
654
879
  result = result.map((row) => ({
655
880
  ...row,
656
- [load.as]: grouped2.get(row[relation2.localKey])
881
+ [load.as]: getByRelationKey(grouped2, row[relation2.localKey])
882
+ }));
883
+ continue;
884
+ }
885
+ if (load.kind === "belongsToMany") {
886
+ const relation2 = load.relation;
887
+ const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
888
+ result = result.map((row) => ({
889
+ ...row,
890
+ [load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
657
891
  }));
658
892
  continue;
659
893
  }
@@ -662,7 +896,7 @@ class RepositoryQuery {
662
896
  const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
663
897
  result = result.map((row) => ({
664
898
  ...row,
665
- [load.as]: grouped2.get(row[relation2.morphIdKey])
899
+ [load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
666
900
  }));
667
901
  continue;
668
902
  }
@@ -670,7 +904,7 @@ class RepositoryQuery {
670
904
  const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
671
905
  result = result.map((row) => ({
672
906
  ...row,
673
- [load.as]: grouped.get(row[relation.foreignKey])
907
+ [load.as]: getByRelationKey(grouped, row[relation.foreignKey])
674
908
  }));
675
909
  }
676
910
  return result;
@@ -341,7 +341,7 @@ function buildOperatorClauses(column, operator, params) {
341
341
  clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
342
342
  }
343
343
  if (operator.ilike !== undefined) {
344
- clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
344
+ clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
345
345
  }
346
346
  if (operator.tsMatch !== undefined) {
347
347
  clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
@@ -56,9 +56,15 @@ class JsonResource {
56
56
 
57
57
  class ResourceCollection extends JsonResource {
58
58
  toArray() {
59
- return {
60
- data: this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item })
61
- };
59
+ const items = this.resource.map((item) => item instanceof JsonResource ? item.toArray() : { ...item });
60
+ const wrap = this.constructor.wrap;
61
+ if (wrap === null) {
62
+ return { data: items };
63
+ }
64
+ return { [wrap]: items };
65
+ }
66
+ toResponse() {
67
+ return { ...this.toArray(), ...this.extra };
62
68
  }
63
69
  }
64
70
  function toResourceCollection(items, transformer) {
@@ -35,7 +35,7 @@ export { withMigrationLock } from "../core/database/migrations/advisoryLock.ts";
35
35
  export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner.ts";
36
36
  export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types.ts";
37
37
  export type { CastType, GlobalScopeFn, ModelConstructor } from "../core/database/model.ts";
38
- export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelRepository, } from "../core/database/model.ts";
38
+ export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "../core/database/model.ts";
39
39
  export { createDatabaseQueryProxy } from "../core/database/queryProxy.ts";
40
40
  export type { BelongsToManyRelation, BelongsToRelation, HasManyRelation, HasOneRelation, MorphManyRelation, MorphOneRelation, MorphToRelation, } from "../core/database/relationships.ts";
41
41
  export { belongsTo, belongsToMany, hasMany, hasOne, indexBelongsToManyRelation, indexBelongsToRelation, indexHasManyRelation, indexHasOneRelation, indexMorphManyRelation, indexMorphOneRelation, indexMorphToRelation, morphMany, morphOne, morphTo, } from "../core/database/relationships.ts";