@happyvertical/smrt-core 0.36.7 → 0.37.0
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/dist/child-accessors.d.ts +1 -1
- package/dist/child-accessors.d.ts.map +1 -1
- package/dist/child-accessors.js +1 -1
- package/dist/child-accessors.js.map +1 -1
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +3 -1
- package/dist/class.js.map +1 -1
- package/dist/collection-cache.d.ts.map +1 -1
- package/dist/collection-cache.js +5 -3
- package/dist/collection-cache.js.map +1 -1
- package/dist/collection.d.ts +39 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +40 -19
- package/dist/collection.js.map +1 -1
- package/dist/decorators/compatibility.d.ts +1 -1
- package/dist/decorators/compatibility.d.ts.map +1 -1
- package/dist/decorators/compatibility.js.map +1 -1
- package/dist/decorators/index.d.ts +4 -4
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js.map +1 -1
- package/dist/hierarchical.d.ts.map +1 -1
- package/dist/hierarchical.js.map +1 -1
- package/dist/junction.d.ts.map +1 -1
- package/dist/junction.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +39 -20
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +2 -2
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest/test-manifest-stub.d.ts.map +1 -1
- package/dist/manifest/test-manifest-stub.js +2301 -629
- package/dist/manifest/test-manifest-stub.js.map +1 -1
- package/dist/manifest.json +39 -20
- package/dist/object.d.ts +64 -17
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +76 -30
- package/dist/object.js.map +1 -1
- package/dist/registry/class-registration.d.ts +3 -3
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +39 -42
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/inheritance-resolver.d.ts +17 -3
- package/dist/registry/inheritance-resolver.d.ts.map +1 -1
- package/dist/registry/inheritance-resolver.js +1 -1
- package/dist/registry/inheritance-resolver.js.map +1 -1
- package/dist/registry/manifest-field-merge.d.ts +17 -3
- package/dist/registry/manifest-field-merge.d.ts.map +1 -1
- package/dist/registry/manifest-field-merge.js +8 -6
- package/dist/registry/manifest-field-merge.js.map +1 -1
- package/dist/registry/schema-builder.d.ts +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/shared-state.d.ts +3 -3
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry/types.d.ts +78 -19
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/validator.d.ts +2 -1
- package/dist/registry/validator.d.ts.map +1 -1
- package/dist/registry/validator.js +38 -39
- package/dist/registry/validator.js.map +1 -1
- package/dist/registry.d.ts +84 -57
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +31 -25
- package/dist/registry.js.map +1 -1
- package/dist/runtime/client.d.ts +6 -6
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/mcp.d.ts +11 -4
- package/dist/runtime/mcp.d.ts.map +1 -1
- package/dist/runtime/mcp.js.map +1 -1
- package/dist/runtime/server.d.ts +29 -5
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +4 -4
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/types.d.ts +12 -12
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +5 -4
- package/dist/system-fields.d.ts +1 -1
- package/dist/system-fields.d.ts.map +1 -1
- package/dist/system-fields.js.map +1 -1
- package/package.json +4 -4
package/dist/object.js
CHANGED
|
@@ -289,12 +289,13 @@ class SmrtObject extends SmrtClass {
|
|
|
289
289
|
const options = this.options;
|
|
290
290
|
if (options.created_at !== void 0) this.created_at = options.created_at;
|
|
291
291
|
if (options.updated_at !== void 0) this.updated_at = options.updated_at;
|
|
292
|
-
if (options._meta_type
|
|
292
|
+
if (typeof options._meta_type === "string") {
|
|
293
293
|
this.setMetaType(options._meta_type);
|
|
294
294
|
}
|
|
295
295
|
const fields = await fieldsFromClass(
|
|
296
296
|
this.constructor
|
|
297
297
|
);
|
|
298
|
+
const writable = this;
|
|
298
299
|
for (const [key, field] of Object.entries(fields)) {
|
|
299
300
|
if (options[key] !== void 0) {
|
|
300
301
|
const clonedValue = this.cloneValue(options[key]);
|
|
@@ -307,7 +308,7 @@ class SmrtObject extends SmrtClass {
|
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
if (!descriptor || descriptor.set || descriptor.writable === true) {
|
|
310
|
-
|
|
311
|
+
writable[key] = clonedValue;
|
|
311
312
|
}
|
|
312
313
|
}
|
|
313
314
|
}
|
|
@@ -463,17 +464,18 @@ class SmrtObject extends SmrtClass {
|
|
|
463
464
|
});
|
|
464
465
|
}
|
|
465
466
|
if (isSTI) {
|
|
466
|
-
|
|
467
|
+
const metaType = formattedData._meta_type;
|
|
468
|
+
if (!metaType) {
|
|
467
469
|
throw new Error(
|
|
468
470
|
`STI validation failed: Missing _meta_type discriminator in database row for ${className}. Ensure the row was saved with STI support enabled.`
|
|
469
471
|
);
|
|
470
472
|
}
|
|
471
|
-
if (!isValidMetaType(
|
|
473
|
+
if (!isValidMetaType(metaType, className)) {
|
|
472
474
|
throw new Error(
|
|
473
|
-
`STI validation failed: Type mismatch when loading ${className}. Database row has _meta_type='${
|
|
475
|
+
`STI validation failed: Type mismatch when loading ${className}. Database row has _meta_type='${metaType}' but expected '${getExpectedMetaType(className)}'. This usually means you're trying to load a row with the wrong class.`
|
|
474
476
|
);
|
|
475
477
|
}
|
|
476
|
-
this.setMetaType(
|
|
478
|
+
this.setMetaType(metaType);
|
|
477
479
|
}
|
|
478
480
|
if (process.env.DEBUG_STI) {
|
|
479
481
|
logger.debug("[loadDataFromDb] Starting field hydration", {
|
|
@@ -483,6 +485,7 @@ class SmrtObject extends SmrtClass {
|
|
|
483
485
|
}
|
|
484
486
|
let hydratedCount = 0;
|
|
485
487
|
let skippedCount = 0;
|
|
488
|
+
const writable = this;
|
|
486
489
|
for (const field in fields) {
|
|
487
490
|
if (Object.hasOwn(fields, field)) {
|
|
488
491
|
let descriptor = Object.getOwnPropertyDescriptor(this, field);
|
|
@@ -501,7 +504,7 @@ class SmrtObject extends SmrtClass {
|
|
|
501
504
|
valueType: typeof value
|
|
502
505
|
});
|
|
503
506
|
}
|
|
504
|
-
|
|
507
|
+
writable[field] = value;
|
|
505
508
|
hydratedCount++;
|
|
506
509
|
} else {
|
|
507
510
|
skippedCount++;
|
|
@@ -555,6 +558,11 @@ class SmrtObject extends SmrtClass {
|
|
|
555
558
|
*
|
|
556
559
|
* @returns Object containing field definitions with current values
|
|
557
560
|
*/
|
|
561
|
+
// Explicit `any` return (documented S4 #1579 leaf): subclasses legitimately
|
|
562
|
+
// OVERRIDE getFields() with domain-specific shapes (e.g. projects'
|
|
563
|
+
// `Project.getFields(): Promise<ProjectField[]>`), which a precise field-map
|
|
564
|
+
// return type would reject. The body below stays internally typed.
|
|
565
|
+
// biome-ignore lint/suspicious/noExplicitAny: subclasses OVERRIDE getFields() with domain shapes (e.g. `Project.getFields(): Promise<ProjectField[]>`); a precise base return type would reject those overrides. S4 #1579.
|
|
558
566
|
async getFields() {
|
|
559
567
|
const className = this.getResolvedClassName();
|
|
560
568
|
const cachedFields = await ObjectRegistry.getAllFields(className);
|
|
@@ -595,7 +603,9 @@ class SmrtObject extends SmrtClass {
|
|
|
595
603
|
* class Article extends SmrtObject {
|
|
596
604
|
* body: string = '';
|
|
597
605
|
*
|
|
598
|
-
* protected transformJSON(
|
|
606
|
+
* protected transformJSON(
|
|
607
|
+
* data: Record<string, unknown>,
|
|
608
|
+
* ): Record<string, unknown> {
|
|
599
609
|
* return {
|
|
600
610
|
* ...data,
|
|
601
611
|
* wordCount: this.body.split(/\s+/).length,
|
|
@@ -636,13 +646,15 @@ class SmrtObject extends SmrtClass {
|
|
|
636
646
|
created_at: this.created_at,
|
|
637
647
|
updated_at: this.updated_at
|
|
638
648
|
};
|
|
649
|
+
const self = this;
|
|
639
650
|
const tableStrategy = ObjectRegistry.getTableStrategy(
|
|
640
651
|
this.getResolvedQualifiedName()
|
|
641
652
|
);
|
|
642
653
|
const isSTI = tableStrategy === "sti";
|
|
654
|
+
const metaData = {};
|
|
643
655
|
if (isSTI) {
|
|
644
656
|
data._meta_type = this.getResolvedQualifiedName();
|
|
645
|
-
data._meta_data =
|
|
657
|
+
data._meta_data = metaData;
|
|
646
658
|
}
|
|
647
659
|
const registered = ObjectRegistry.getClass(className);
|
|
648
660
|
let registeredFields = registered?.inheritedFields || ObjectRegistry.getFields(className);
|
|
@@ -665,7 +677,7 @@ class SmrtObject extends SmrtClass {
|
|
|
665
677
|
}
|
|
666
678
|
for (const key of registeredFields.keys()) {
|
|
667
679
|
if (key.startsWith("_") || key === "id" || key === "slug" || key === "context" || key === "created_at" || key === "updated_at" || key === "options" || // Skip options object (not a database column)
|
|
668
|
-
typeof
|
|
680
|
+
typeof self[key] === "function") {
|
|
669
681
|
continue;
|
|
670
682
|
}
|
|
671
683
|
const fieldDef = registeredFields.get(key);
|
|
@@ -675,21 +687,22 @@ class SmrtObject extends SmrtClass {
|
|
|
675
687
|
if (fieldDef && (fieldDef.type === "oneToMany" || fieldDef.type === "manyToMany")) {
|
|
676
688
|
continue;
|
|
677
689
|
}
|
|
678
|
-
const prop =
|
|
690
|
+
const prop = self[key];
|
|
679
691
|
const value = this.getPropertyValue(key);
|
|
680
692
|
if (value === void 0) {
|
|
681
693
|
const fieldType = prop && typeof prop === "object" && "type" in prop && prop.type || fieldDef?.type;
|
|
682
694
|
if (fieldType === "text") {
|
|
683
|
-
const
|
|
695
|
+
const propTenancy = prop && typeof prop === "object" && "__tenancy" in prop ? prop.__tenancy : void 0;
|
|
696
|
+
const hasTenancyMarker = propTenancy?.isTenantIdField || fieldDef?.__tenancy?.isTenantIdField || fieldDef?._meta?.__tenancy?.isTenantIdField;
|
|
684
697
|
if (hasTenancyMarker) {
|
|
685
698
|
if (isSTI && fieldDef?.type === "meta") {
|
|
686
|
-
|
|
699
|
+
metaData[key] = null;
|
|
687
700
|
} else {
|
|
688
701
|
data[key] = null;
|
|
689
702
|
}
|
|
690
703
|
} else {
|
|
691
704
|
if (isSTI && fieldDef?.type === "meta") {
|
|
692
|
-
|
|
705
|
+
metaData[key] = "";
|
|
693
706
|
} else {
|
|
694
707
|
data[key] = "";
|
|
695
708
|
}
|
|
@@ -697,7 +710,7 @@ class SmrtObject extends SmrtClass {
|
|
|
697
710
|
} else if (fieldType === "json") {
|
|
698
711
|
const defaultValue = fieldDef?.default ?? null;
|
|
699
712
|
if (isSTI && fieldDef?.type === "meta") {
|
|
700
|
-
|
|
713
|
+
metaData[key] = defaultValue;
|
|
701
714
|
} else {
|
|
702
715
|
data[key] = defaultValue;
|
|
703
716
|
}
|
|
@@ -705,7 +718,7 @@ class SmrtObject extends SmrtClass {
|
|
|
705
718
|
continue;
|
|
706
719
|
}
|
|
707
720
|
if (isSTI && fieldDef && fieldDef.type === "meta") {
|
|
708
|
-
|
|
721
|
+
metaData[key] = value;
|
|
709
722
|
} else {
|
|
710
723
|
data[key] = value;
|
|
711
724
|
}
|
|
@@ -899,13 +912,14 @@ class SmrtObject extends SmrtClass {
|
|
|
899
912
|
*/
|
|
900
913
|
async getSlug() {
|
|
901
914
|
if (!this.slug) {
|
|
915
|
+
const self = this;
|
|
902
916
|
let sourceField = null;
|
|
903
|
-
if (
|
|
904
|
-
sourceField = String(
|
|
905
|
-
} else if (
|
|
906
|
-
sourceField = String(
|
|
907
|
-
} else if (
|
|
908
|
-
sourceField = String(
|
|
917
|
+
if (self.name) {
|
|
918
|
+
sourceField = String(self.name);
|
|
919
|
+
} else if (self.title) {
|
|
920
|
+
sourceField = String(self.title);
|
|
921
|
+
} else if (self.label) {
|
|
922
|
+
sourceField = String(self.label);
|
|
909
923
|
} else if (this.id) {
|
|
910
924
|
sourceField = String(this.id);
|
|
911
925
|
}
|
|
@@ -1263,9 +1277,12 @@ See issue #377: https://github.com/happyvertical/smrt/issues/377`
|
|
|
1263
1277
|
}
|
|
1264
1278
|
return;
|
|
1265
1279
|
}
|
|
1266
|
-
const fields = await fieldsFromClass(
|
|
1280
|
+
const fields = await fieldsFromClass(
|
|
1281
|
+
this.constructor
|
|
1282
|
+
);
|
|
1267
1283
|
for (const [fieldName, field] of Object.entries(fields)) {
|
|
1268
|
-
|
|
1284
|
+
const options = field?.options;
|
|
1285
|
+
if (options?.required) {
|
|
1269
1286
|
const value = this.getFieldValue(fieldName);
|
|
1270
1287
|
if (value === null || value === void 0 || value === "") {
|
|
1271
1288
|
throw ValidationError.requiredField(fieldName, className);
|
|
@@ -1822,6 +1839,26 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
1822
1839
|
isRelatedLoaded(fieldName) {
|
|
1823
1840
|
return this._loadedRelationships.has(fieldName);
|
|
1824
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Internal: seed the lazy-load relationship cache from an external batch
|
|
1844
|
+
* loader.
|
|
1845
|
+
*
|
|
1846
|
+
* `SmrtCollection`'s eager `include:` and `getOrUpsert` paths resolve
|
|
1847
|
+
* relationships in bulk and need to prime each instance's cache so a later
|
|
1848
|
+
* {@link loadRelated}/{@link loadRelatedMany} call returns the already-fetched
|
|
1849
|
+
* value instead of re-querying. This method is the sanctioned write path into
|
|
1850
|
+
* the otherwise-private `_loadedRelationships` map, so collaborators never have
|
|
1851
|
+
* to structurally cast into the private field (which the no-private-reach-ins
|
|
1852
|
+
* convention forbids). The leading underscore keeps it out of the generated
|
|
1853
|
+
* REST/CLI/MCP surface, like other framework-internal members.
|
|
1854
|
+
*
|
|
1855
|
+
* @param fieldName - Relationship field whose value is being cached
|
|
1856
|
+
* @param value - The loaded relationship (a `SmrtObject`, an array of them, or
|
|
1857
|
+
* `null`), stored verbatim
|
|
1858
|
+
*/
|
|
1859
|
+
_setLoadedRelationship(fieldName, value) {
|
|
1860
|
+
this._loadedRelationships.set(fieldName, value);
|
|
1861
|
+
}
|
|
1825
1862
|
/**
|
|
1826
1863
|
* Lazy-loads a `foreignKey` or `crossPackageRef` relationship and caches the
|
|
1827
1864
|
* result.
|
|
@@ -1859,6 +1896,10 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
1859
1896
|
*
|
|
1860
1897
|
* @see {@link getRelated} for a convenience wrapper that auto-detects relationship type
|
|
1861
1898
|
*/
|
|
1899
|
+
// Returns a polymorphic related object whose concrete subclass type only the
|
|
1900
|
+
// CALLER knows (e.g. `metafield.validateValue(...)`). Typed `any` so callers
|
|
1901
|
+
// can use the concrete API without a cast at every site; a generic with a
|
|
1902
|
+
// `SmrtObject` default wouldn't help untyped callers. Documented S4 #1579 leaf.
|
|
1862
1903
|
async loadRelated(fieldName, opts) {
|
|
1863
1904
|
if (this._loadedRelationships.has(fieldName)) {
|
|
1864
1905
|
const cached = this._loadedRelationships.get(fieldName);
|
|
@@ -1995,6 +2036,8 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
1995
2036
|
* console.log(`${orders.length} orders found`);
|
|
1996
2037
|
* ```
|
|
1997
2038
|
*/
|
|
2039
|
+
// Polymorphic related objects; concrete type is caller-known (see loadRelated).
|
|
2040
|
+
// Documented S4 #1579 leaf.
|
|
1998
2041
|
async loadRelatedMany(fieldName, opts) {
|
|
1999
2042
|
if (this._loadedRelationships.has(fieldName)) {
|
|
2000
2043
|
const cached = this._loadedRelationships.get(fieldName);
|
|
@@ -2104,7 +2147,8 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
2104
2147
|
fieldName
|
|
2105
2148
|
);
|
|
2106
2149
|
const opts = relationship.options || {};
|
|
2107
|
-
const
|
|
2150
|
+
const optsMeta = opts._meta && typeof opts._meta === "object" ? opts._meta : void 0;
|
|
2151
|
+
const through = decorator?.through ?? opts.through ?? optsMeta?.through;
|
|
2108
2152
|
if (!through) {
|
|
2109
2153
|
throw RuntimeError.invalidState(
|
|
2110
2154
|
`manyToMany field ${fieldName} on ${relationship.sourceClass} is missing the 'through' join table name`,
|
|
@@ -2113,12 +2157,12 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
2113
2157
|
}
|
|
2114
2158
|
const targetSimpleName = relationship.targetClass.includes(":") ? relationship.targetClass.split(":").pop() : relationship.targetClass;
|
|
2115
2159
|
const sourceSimpleName = relationship.sourceClass.includes(":") ? relationship.sourceClass.split(":").pop() : relationship.sourceClass;
|
|
2116
|
-
const sourceColumn = decorator?.sourceKey ?? opts.sourceKey ??
|
|
2117
|
-
const targetColumn = decorator?.targetKey ?? opts.targetKey ??
|
|
2160
|
+
const sourceColumn = decorator?.sourceKey ?? opts.sourceKey ?? optsMeta?.sourceKey ?? `${toSnakeCase(sourceSimpleName)}_id`;
|
|
2161
|
+
const targetColumn = decorator?.targetKey ?? opts.targetKey ?? optsMeta?.targetKey ?? `${toSnakeCase(targetSimpleName)}_id`;
|
|
2118
2162
|
return {
|
|
2119
2163
|
through: String(through),
|
|
2120
|
-
sourceColumn,
|
|
2121
|
-
targetColumn,
|
|
2164
|
+
sourceColumn: String(sourceColumn),
|
|
2165
|
+
targetColumn: String(targetColumn),
|
|
2122
2166
|
targetClassName: relationship.targetClass
|
|
2123
2167
|
};
|
|
2124
2168
|
}
|
|
@@ -2145,6 +2189,8 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
2145
2189
|
* const orders = await customer.getRelated('orders');
|
|
2146
2190
|
* ```
|
|
2147
2191
|
*/
|
|
2192
|
+
// Polymorphic related object(s); concrete type is caller-known (see
|
|
2193
|
+
// loadRelated). Documented S4 #1579 leaf.
|
|
2148
2194
|
async getRelated(fieldName, opts) {
|
|
2149
2195
|
if (this._loadedRelationships.has(fieldName)) {
|
|
2150
2196
|
const cached = this._loadedRelationships.get(fieldName);
|
|
@@ -2337,7 +2383,7 @@ Based on the content body, please follow the instructions and provide a response
|
|
|
2337
2383
|
}
|
|
2338
2384
|
if (result) {
|
|
2339
2385
|
try {
|
|
2340
|
-
return JSON.parse(result.value);
|
|
2386
|
+
return JSON.parse(String(result.value));
|
|
2341
2387
|
} catch (error) {
|
|
2342
2388
|
logger.warn("Skipping corrupted _smrt_contexts value in recall()", {
|
|
2343
2389
|
ownerClass: this._className,
|