@happyvertical/smrt-core 0.30.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/AGENTS.md +124 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +265 -0
- package/bin/smrt-prebuild.js +26 -0
- package/dist/__tests__/fixtures/advisor-test-classes.d.ts +28 -0
- package/dist/__tests__/fixtures/advisor-test-classes.d.ts.map +1 -0
- package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts +28 -0
- package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures/inheritance-test-classes.d.ts +43 -0
- package/dist/__tests__/fixtures/inheritance-test-classes.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts +18 -0
- package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts.map +1 -0
- package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts +15 -0
- package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures/object-spec-test-classes.d.ts +13 -0
- package/dist/__tests__/fixtures/object-spec-test-classes.d.ts.map +1 -0
- package/dist/__tests__/fixtures/registry-test-classes.d.ts +23 -0
- package/dist/__tests__/fixtures/registry-test-classes.d.ts.map +1 -0
- package/dist/adapters/ai-usage.d.ts +23 -0
- package/dist/adapters/ai-usage.d.ts.map +1 -0
- package/dist/adapters/ai-usage.js +105 -0
- package/dist/adapters/ai-usage.js.map +1 -0
- package/dist/adapters/cost-rates.d.ts +20 -0
- package/dist/adapters/cost-rates.d.ts.map +1 -0
- package/dist/adapters/cost-rates.js +40 -0
- package/dist/adapters/cost-rates.js.map +1 -0
- package/dist/adapters/index.d.ts +19 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/metrics.d.ts +111 -0
- package/dist/adapters/metrics.d.ts.map +1 -0
- package/dist/adapters/metrics.js +169 -0
- package/dist/adapters/metrics.js.map +1 -0
- package/dist/adapters/pubsub.d.ts +124 -0
- package/dist/adapters/pubsub.d.ts.map +1 -0
- package/dist/adapters/pubsub.js +121 -0
- package/dist/adapters/pubsub.js.map +1 -0
- package/dist/browser.d.ts +32 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +68 -0
- package/dist/browser.js.map +1 -0
- package/dist/child-accessors.d.ts +27 -0
- package/dist/child-accessors.d.ts.map +1 -0
- package/dist/child-accessors.js +35 -0
- package/dist/child-accessors.js.map +1 -0
- package/dist/class.d.ts +313 -0
- package/dist/class.d.ts.map +1 -0
- package/dist/class.js +896 -0
- package/dist/class.js.map +1 -0
- package/dist/collection-cache.d.ts +110 -0
- package/dist/collection-cache.d.ts.map +1 -0
- package/dist/collection-cache.js +187 -0
- package/dist/collection-cache.js.map +1 -0
- package/dist/collection.d.ts +894 -0
- package/dist/collection.d.ts.map +1 -0
- package/dist/collection.js +1987 -0
- package/dist/collection.js.map +1 -0
- package/dist/config/global-config.d.ts +3 -0
- package/dist/config/global-config.d.ts.map +1 -0
- package/dist/config/global-config.js +19 -0
- package/dist/config/global-config.js.map +1 -0
- package/dist/config.d.ts +145 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +57 -0
- package/dist/config.js.map +1 -0
- package/dist/consumer-plugin/index.d.ts +22 -0
- package/dist/consumer-plugin/index.d.ts.map +1 -0
- package/dist/consumer-plugin/index.js +452 -0
- package/dist/consumer-plugin/index.js.map +1 -0
- package/dist/consumer-plugin.d.ts +8 -0
- package/dist/consumer-plugin.d.ts.map +1 -0
- package/dist/consumer-plugin.js +5 -0
- package/dist/consumer-plugin.js.map +1 -0
- package/dist/database.d.ts +95 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +32 -0
- package/dist/database.js.map +1 -0
- package/dist/decorators/compatibility.d.ts +14 -0
- package/dist/decorators/compatibility.d.ts.map +1 -0
- package/dist/decorators/compatibility.js +111 -0
- package/dist/decorators/compatibility.js.map +1 -0
- package/dist/decorators/index.d.ts +381 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +104 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/dispatch/bus.d.ts +306 -0
- package/dist/dispatch/bus.d.ts.map +1 -0
- package/dist/dispatch/bus.js +583 -0
- package/dist/dispatch/bus.js.map +1 -0
- package/dist/dispatch/collections/DispatchSubscriptions.d.ts +79 -0
- package/dist/dispatch/collections/DispatchSubscriptions.d.ts.map +1 -0
- package/dist/dispatch/collections/DispatchSubscriptions.js +243 -0
- package/dist/dispatch/collections/DispatchSubscriptions.js.map +1 -0
- package/dist/dispatch/collections/Dispatches.d.ts +98 -0
- package/dist/dispatch/collections/Dispatches.d.ts.map +1 -0
- package/dist/dispatch/collections/Dispatches.js +358 -0
- package/dist/dispatch/collections/Dispatches.js.map +1 -0
- package/dist/dispatch/index.d.ts +47 -0
- package/dist/dispatch/index.d.ts.map +1 -0
- package/dist/dispatch/models/Dispatch.d.ts +101 -0
- package/dist/dispatch/models/Dispatch.d.ts.map +1 -0
- package/dist/dispatch/models/Dispatch.js +162 -0
- package/dist/dispatch/models/Dispatch.js.map +1 -0
- package/dist/dispatch/models/DispatchSubscription.d.ts +83 -0
- package/dist/dispatch/models/DispatchSubscription.d.ts.map +1 -0
- package/dist/dispatch/models/DispatchSubscription.js +112 -0
- package/dist/dispatch/models/DispatchSubscription.js.map +1 -0
- package/dist/dispatch/tenant-resolver.d.ts +98 -0
- package/dist/dispatch/tenant-resolver.d.ts.map +1 -0
- package/dist/dispatch/tenant-resolver.js +32 -0
- package/dist/dispatch/tenant-resolver.js.map +1 -0
- package/dist/dispatch/types.d.ts +149 -0
- package/dist/dispatch/types.d.ts.map +1 -0
- package/dist/embeddings/hash.d.ts +33 -0
- package/dist/embeddings/hash.d.ts.map +1 -0
- package/dist/embeddings/hash.js +37 -0
- package/dist/embeddings/hash.js.map +1 -0
- package/dist/embeddings/index.d.ts +36 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/provider.d.ts +75 -0
- package/dist/embeddings/provider.d.ts.map +1 -0
- package/dist/embeddings/provider.js +170 -0
- package/dist/embeddings/provider.js.map +1 -0
- package/dist/embeddings/similarity.d.ts +47 -0
- package/dist/embeddings/similarity.d.ts.map +1 -0
- package/dist/embeddings/similarity.js +64 -0
- package/dist/embeddings/similarity.js.map +1 -0
- package/dist/embeddings/storage.d.ts +125 -0
- package/dist/embeddings/storage.d.ts.map +1 -0
- package/dist/embeddings/storage.js +283 -0
- package/dist/embeddings/storage.js.map +1 -0
- package/dist/embeddings/types.d.ts +250 -0
- package/dist/embeddings/types.d.ts.map +1 -0
- package/dist/errors.d.ts +363 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +669 -0
- package/dist/errors.js.map +1 -0
- package/dist/generators/cli.d.ts +162 -0
- package/dist/generators/cli.d.ts.map +1 -0
- package/dist/generators/cli.js +462 -0
- package/dist/generators/cli.js.map +1 -0
- package/dist/generators/index.d.ts +13 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/mcp-runtime-template.d.ts +60 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -0
- package/dist/generators/mcp-runtime-template.js +509 -0
- package/dist/generators/mcp-runtime-template.js.map +1 -0
- package/dist/generators/mcp.d.ts +231 -0
- package/dist/generators/mcp.d.ts.map +1 -0
- package/dist/generators/mcp.js +1220 -0
- package/dist/generators/mcp.js.map +1 -0
- package/dist/generators/rest.d.ts +171 -0
- package/dist/generators/rest.d.ts.map +1 -0
- package/dist/generators/rest.js +591 -0
- package/dist/generators/rest.js.map +1 -0
- package/dist/generators/swagger.d.ts +21 -0
- package/dist/generators/swagger.d.ts.map +1 -0
- package/dist/generators/swagger.js +307 -0
- package/dist/generators/swagger.js.map +1 -0
- package/dist/generators/tenant-gate.d.ts +74 -0
- package/dist/generators/tenant-gate.d.ts.map +1 -0
- package/dist/generators/tenant-gate.js +15 -0
- package/dist/generators/tenant-gate.js.map +1 -0
- package/dist/generators.d.ts +8 -0
- package/dist/generators.d.ts.map +1 -0
- package/dist/generators.js +19 -0
- package/dist/generators.js.map +1 -0
- package/dist/hierarchical.d.ts +103 -0
- package/dist/hierarchical.d.ts.map +1 -0
- package/dist/hierarchical.js +184 -0
- package/dist/hierarchical.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptors.d.ts +251 -0
- package/dist/interceptors.d.ts.map +1 -0
- package/dist/interceptors.js +259 -0
- package/dist/interceptors.js.map +1 -0
- package/dist/junction.d.ts +99 -0
- package/dist/junction.d.ts.map +1 -0
- package/dist/junction.js +136 -0
- package/dist/junction.js.map +1 -0
- package/dist/knowledge.d.ts +11 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +310 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/lazy-config.d.ts +160 -0
- package/dist/lazy-config.d.ts.map +1 -0
- package/dist/lazy-config.js +146 -0
- package/dist/lazy-config.js.map +1 -0
- package/dist/manifest/discover-base-classes.d.ts +78 -0
- package/dist/manifest/discover-base-classes.d.ts.map +1 -0
- package/dist/manifest/discover-base-classes.js +85 -0
- package/dist/manifest/discover-base-classes.js.map +1 -0
- package/dist/manifest/discover-smrt-packages.d.ts +48 -0
- package/dist/manifest/discover-smrt-packages.d.ts.map +1 -0
- package/dist/manifest/discover-smrt-packages.js +361 -0
- package/dist/manifest/discover-smrt-packages.js.map +1 -0
- package/dist/manifest/generator.d.ts +93 -0
- package/dist/manifest/generator.d.ts.map +1 -0
- package/dist/manifest/generator.js +380 -0
- package/dist/manifest/generator.js.map +1 -0
- package/dist/manifest/index.d.ts +16 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +51 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/manager.d.ts +51 -0
- package/dist/manifest/manager.d.ts.map +1 -0
- package/dist/manifest/manager.js +89 -0
- package/dist/manifest/manager.js.map +1 -0
- package/dist/manifest/manifest-loader.d.ts +187 -0
- package/dist/manifest/manifest-loader.d.ts.map +1 -0
- package/dist/manifest/manifest-loader.js +847 -0
- package/dist/manifest/manifest-loader.js.map +1 -0
- package/dist/manifest/sources/composite.d.ts +22 -0
- package/dist/manifest/sources/composite.d.ts.map +1 -0
- package/dist/manifest/sources/composite.js +60 -0
- package/dist/manifest/sources/composite.js.map +1 -0
- package/dist/manifest/sources/embedded.d.ts +7 -0
- package/dist/manifest/sources/embedded.d.ts.map +1 -0
- package/dist/manifest/sources/embedded.js +30 -0
- package/dist/manifest/sources/embedded.js.map +1 -0
- package/dist/manifest/sources/explicit-paths.d.ts +17 -0
- package/dist/manifest/sources/explicit-paths.d.ts.map +1 -0
- package/dist/manifest/sources/explicit-paths.js +35 -0
- package/dist/manifest/sources/explicit-paths.js.map +1 -0
- package/dist/manifest/sources/fallback.d.ts +25 -0
- package/dist/manifest/sources/fallback.d.ts.map +1 -0
- package/dist/manifest/sources/fallback.js +63 -0
- package/dist/manifest/sources/fallback.js.map +1 -0
- package/dist/manifest/sources/index.d.ts +17 -0
- package/dist/manifest/sources/index.d.ts.map +1 -0
- package/dist/manifest/sources/local-test.d.ts +7 -0
- package/dist/manifest/sources/local-test.d.ts.map +1 -0
- package/dist/manifest/sources/local-test.js +21 -0
- package/dist/manifest/sources/local-test.js.map +1 -0
- package/dist/manifest/sources/static.d.ts +7 -0
- package/dist/manifest/sources/static.d.ts.map +1 -0
- package/dist/manifest/sources/static.js +19 -0
- package/dist/manifest/sources/static.js.map +1 -0
- package/dist/manifest/sources/test.d.ts +7 -0
- package/dist/manifest/sources/test.d.ts.map +1 -0
- package/dist/manifest/sources/test.js +21 -0
- package/dist/manifest/sources/test.js.map +1 -0
- package/dist/manifest/sources/types.d.ts +79 -0
- package/dist/manifest/sources/types.d.ts.map +1 -0
- package/dist/manifest/sources/types.js +61 -0
- package/dist/manifest/sources/types.js.map +1 -0
- package/dist/manifest/static-manifest.d.ts +4 -0
- package/dist/manifest/static-manifest.d.ts.map +1 -0
- package/dist/manifest/static-manifest.js +1535 -0
- package/dist/manifest/static-manifest.js.map +1 -0
- package/dist/manifest/store.d.ts +111 -0
- package/dist/manifest/store.d.ts.map +1 -0
- package/dist/manifest/store.js +431 -0
- package/dist/manifest/store.js.map +1 -0
- package/dist/manifest/test-manifest-loader.d.ts +3 -0
- package/dist/manifest/test-manifest-loader.d.ts.map +1 -0
- package/dist/manifest/test-manifest-stub.d.ts +4 -0
- package/dist/manifest/test-manifest-stub.d.ts.map +1 -0
- package/dist/manifest/test-manifest-stub.js +80013 -0
- package/dist/manifest/test-manifest-stub.js.map +1 -0
- package/dist/manifest.d.ts +8 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +20 -0
- package/dist/manifest.js.map +1 -0
- package/dist/manifest.json +1489 -0
- package/dist/mcp-advisor/index.d.ts +499 -0
- package/dist/mcp-advisor/index.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/add-ai-methods.d.ts +6 -0
- package/dist/mcp-advisor/tools/add-ai-methods.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/configure-decorators.d.ts +6 -0
- package/dist/mcp-advisor/tools/configure-decorators.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/generate-collection.d.ts +6 -0
- package/dist/mcp-advisor/tools/generate-collection.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/generate-field-definitions.d.ts +6 -0
- package/dist/mcp-advisor/tools/generate-field-definitions.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/generate-smrt-class.d.ts +6 -0
- package/dist/mcp-advisor/tools/generate-smrt-class.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/get-object-config.d.ts +6 -0
- package/dist/mcp-advisor/tools/get-object-config.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/get-object-schema.d.ts +10 -0
- package/dist/mcp-advisor/tools/get-object-schema.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/list-registered-objects.d.ts +9 -0
- package/dist/mcp-advisor/tools/list-registered-objects.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts +9 -0
- package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts +9 -0
- package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts.map +1 -0
- package/dist/mcp-advisor/tools/validate-smrt-object.d.ts +6 -0
- package/dist/mcp-advisor/tools/validate-smrt-object.d.ts.map +1 -0
- package/dist/mcp-advisor/types.d.ts +209 -0
- package/dist/mcp-advisor/types.d.ts.map +1 -0
- package/dist/migrations/backfill-tracker.d.ts +84 -0
- package/dist/migrations/backfill-tracker.d.ts.map +1 -0
- package/dist/migrations/backfill-tracker.js +118 -0
- package/dist/migrations/backfill-tracker.js.map +1 -0
- package/dist/migrations/checksum.d.ts +43 -0
- package/dist/migrations/checksum.d.ts.map +1 -0
- package/dist/migrations/checksum.js +32 -0
- package/dist/migrations/checksum.js.map +1 -0
- package/dist/migrations/differ.d.ts +186 -0
- package/dist/migrations/differ.d.ts.map +1 -0
- package/dist/migrations/differ.js +601 -0
- package/dist/migrations/differ.js.map +1 -0
- package/dist/migrations/generator.d.ts +133 -0
- package/dist/migrations/generator.d.ts.map +1 -0
- package/dist/migrations/generator.js +328 -0
- package/dist/migrations/generator.js.map +1 -0
- package/dist/migrations/index.d.ts +20 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/orchestrate.d.ts +148 -0
- package/dist/migrations/orchestrate.d.ts.map +1 -0
- package/dist/migrations/orchestrate.js +118 -0
- package/dist/migrations/orchestrate.js.map +1 -0
- package/dist/migrations/tracker.d.ts +134 -0
- package/dist/migrations/tracker.d.ts.map +1 -0
- package/dist/migrations/tracker.js +624 -0
- package/dist/migrations/tracker.js.map +1 -0
- package/dist/migrations/types.d.ts +221 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations.d.ts +7 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +26 -0
- package/dist/migrations.js.map +1 -0
- package/dist/node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js +56 -0
- package/dist/node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js.map +1 -0
- package/dist/node_modules/.pnpm/brace-expansion@5.0.5/node_modules/brace-expansion/dist/esm/index.js +163 -0
- package/dist/node_modules/.pnpm/brace-expansion@5.0.5/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js +13 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/ast.js +654 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/ast.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/brace-expressions.js +111 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/escape.js +10 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/escape.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/index.js +824 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/index.js.map +1 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/unescape.js +10 -0
- package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
- package/dist/object.d.ts +1202 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/object.js +2731 -0
- package/dist/object.js.map +1 -0
- package/dist/polymorphic-association.d.ts +69 -0
- package/dist/polymorphic-association.d.ts.map +1 -0
- package/dist/polymorphic-association.js +96 -0
- package/dist/polymorphic-association.js.map +1 -0
- package/dist/prebuild/cli.d.ts +7 -0
- package/dist/prebuild/cli.d.ts.map +1 -0
- package/dist/prebuild/cli.js +29 -0
- package/dist/prebuild/cli.js.map +1 -0
- package/dist/prebuild/index.d.ts +22 -0
- package/dist/prebuild/index.d.ts.map +1 -0
- package/dist/prebuild/index.js +239 -0
- package/dist/prebuild/index.js.map +1 -0
- package/dist/prebuild.d.ts +8 -0
- package/dist/prebuild.d.ts.map +1 -0
- package/dist/prebuild.js +6 -0
- package/dist/prebuild.js.map +1 -0
- package/dist/registry/cache-config.d.ts +13 -0
- package/dist/registry/cache-config.d.ts.map +1 -0
- package/dist/registry/cache-config.js +17 -0
- package/dist/registry/cache-config.js.map +1 -0
- package/dist/registry/class-registration.d.ts +31 -0
- package/dist/registry/class-registration.d.ts.map +1 -0
- package/dist/registry/class-registration.js +1074 -0
- package/dist/registry/class-registration.js.map +1 -0
- package/dist/registry/collection-resolution.d.ts +45 -0
- package/dist/registry/collection-resolution.d.ts.map +1 -0
- package/dist/registry/collection-resolution.js +121 -0
- package/dist/registry/collection-resolution.js.map +1 -0
- package/dist/registry/collision-policy.d.ts +179 -0
- package/dist/registry/collision-policy.d.ts.map +1 -0
- package/dist/registry/collision-policy.js +153 -0
- package/dist/registry/collision-policy.js.map +1 -0
- package/dist/registry/diagnostics.d.ts +58 -0
- package/dist/registry/diagnostics.d.ts.map +1 -0
- package/dist/registry/diagnostics.js +54 -0
- package/dist/registry/diagnostics.js.map +1 -0
- package/dist/registry/embedding-manager.d.ts +23 -0
- package/dist/registry/embedding-manager.d.ts.map +1 -0
- package/dist/registry/embedding-manager.js +62 -0
- package/dist/registry/embedding-manager.js.map +1 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/inheritance-resolver.d.ts +13 -0
- package/dist/registry/inheritance-resolver.d.ts.map +1 -0
- package/dist/registry/inheritance-resolver.js +244 -0
- package/dist/registry/inheritance-resolver.js.map +1 -0
- package/dist/registry/manifest-field-merge.d.ts +4 -0
- package/dist/registry/manifest-field-merge.d.ts.map +1 -0
- package/dist/registry/manifest-field-merge.js +82 -0
- package/dist/registry/manifest-field-merge.js.map +1 -0
- package/dist/registry/name-resolver.d.ts +102 -0
- package/dist/registry/name-resolver.d.ts.map +1 -0
- package/dist/registry/name-resolver.js +241 -0
- package/dist/registry/name-resolver.js.map +1 -0
- package/dist/registry/relationship-graph.d.ts +16 -0
- package/dist/registry/relationship-graph.d.ts.map +1 -0
- package/dist/registry/relationship-graph.js +79 -0
- package/dist/registry/relationship-graph.js.map +1 -0
- package/dist/registry/schema-builder.d.ts +113 -0
- package/dist/registry/schema-builder.d.ts.map +1 -0
- package/dist/registry/schema-builder.js +474 -0
- package/dist/registry/schema-builder.js.map +1 -0
- package/dist/registry/shared-state.d.ts +62 -0
- package/dist/registry/shared-state.d.ts.map +1 -0
- package/dist/registry/shared-state.js +135 -0
- package/dist/registry/shared-state.js.map +1 -0
- package/dist/registry/types.d.ts +667 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/validator.d.ts +13 -0
- package/dist/registry/validator.d.ts.map +1 -0
- package/dist/registry/validator.js +138 -0
- package/dist/registry/validator.js.map +1 -0
- package/dist/registry.d.ts +1358 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +2301 -0
- package/dist/registry.js.map +1 -0
- package/dist/runtime/client.d.ts +34 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +104 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/index.d.ts +10 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/mcp.d.ts +47 -0
- package/dist/runtime/mcp.d.ts.map +1 -0
- package/dist/runtime/mcp.js +72 -0
- package/dist/runtime/mcp.js.map +1 -0
- package/dist/runtime/server.d.ts +92 -0
- package/dist/runtime/server.d.ts.map +1 -0
- package/dist/runtime/server.js +390 -0
- package/dist/runtime/server.js.map +1 -0
- package/dist/runtime/types.d.ts +58 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime.d.ts +8 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +10 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scanner/import-scanner.d.ts +7 -0
- package/dist/scanner/import-scanner.d.ts.map +1 -0
- package/dist/scanner/index.d.ts +12 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/manifest-generator.d.ts +304 -0
- package/dist/scanner/manifest-generator.d.ts.map +1 -0
- package/dist/scanner/manifest-generator.js +1707 -0
- package/dist/scanner/manifest-generator.js.map +1 -0
- package/dist/scanner/test-file-patterns.d.ts +18 -0
- package/dist/scanner/test-file-patterns.d.ts.map +1 -0
- package/dist/scanner/test-file-patterns.js +16 -0
- package/dist/scanner/test-file-patterns.js.map +1 -0
- package/dist/scanner/types.d.ts +313 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +2 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/scanner.d.ts +6 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +6 -0
- package/dist/scanner.js.map +1 -0
- package/dist/schema/code-generator.d.ts +53 -0
- package/dist/schema/code-generator.d.ts.map +1 -0
- package/dist/schema/ddl/base-strategy.d.ts +80 -0
- package/dist/schema/ddl/base-strategy.d.ts.map +1 -0
- package/dist/schema/ddl/base-strategy.js +240 -0
- package/dist/schema/ddl/base-strategy.js.map +1 -0
- package/dist/schema/ddl/duckdb-strategy.d.ts +33 -0
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -0
- package/dist/schema/ddl/duckdb-strategy.js +74 -0
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -0
- package/dist/schema/ddl/index.d.ts +53 -0
- package/dist/schema/ddl/index.d.ts.map +1 -0
- package/dist/schema/ddl/index.js +80 -0
- package/dist/schema/ddl/index.js.map +1 -0
- package/dist/schema/ddl/json-duckdb-strategy.d.ts +8 -0
- package/dist/schema/ddl/json-duckdb-strategy.d.ts.map +1 -0
- package/dist/schema/ddl/json-duckdb-strategy.js +14 -0
- package/dist/schema/ddl/json-duckdb-strategy.js.map +1 -0
- package/dist/schema/ddl/postgres-strategy.d.ts +29 -0
- package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -0
- package/dist/schema/ddl/postgres-strategy.js +102 -0
- package/dist/schema/ddl/postgres-strategy.js.map +1 -0
- package/dist/schema/ddl/sqlite-strategy.d.ts +38 -0
- package/dist/schema/ddl/sqlite-strategy.d.ts.map +1 -0
- package/dist/schema/ddl/sqlite-strategy.js +74 -0
- package/dist/schema/ddl/sqlite-strategy.js.map +1 -0
- package/dist/schema/ddl/types.d.ts +114 -0
- package/dist/schema/ddl/types.d.ts.map +1 -0
- package/dist/schema/generator.d.ts +176 -0
- package/dist/schema/generator.d.ts.map +1 -0
- package/dist/schema/generator.js +1076 -0
- package/dist/schema/generator.js.map +1 -0
- package/dist/schema/index-utils.d.ts +19 -0
- package/dist/schema/index-utils.d.ts.map +1 -0
- package/dist/schema/index-utils.js +32 -0
- package/dist/schema/index-utils.js.map +1 -0
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/override-system.d.ts +43 -0
- package/dist/schema/override-system.d.ts.map +1 -0
- package/dist/schema/schema-aggregator.d.ts +112 -0
- package/dist/schema/schema-aggregator.d.ts.map +1 -0
- package/dist/schema/schema-manager.d.ts +95 -0
- package/dist/schema/schema-manager.d.ts.map +1 -0
- package/dist/schema/schema-manager.js +283 -0
- package/dist/schema/schema-manager.js.map +1 -0
- package/dist/schema/sql-identifiers.d.ts +107 -0
- package/dist/schema/sql-identifiers.d.ts.map +1 -0
- package/dist/schema/sql-identifiers.js +190 -0
- package/dist/schema/sql-identifiers.js.map +1 -0
- package/dist/schema/table-verifier.d.ts +10 -0
- package/dist/schema/table-verifier.d.ts.map +1 -0
- package/dist/schema/table-verifier.js +37 -0
- package/dist/schema/table-verifier.js.map +1 -0
- package/dist/schema/types.d.ts +241 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/utils.d.ts +32 -0
- package/dist/schema/utils.d.ts.map +1 -0
- package/dist/schema/utils.js +134 -0
- package/dist/schema/utils.js.map +1 -0
- package/dist/scripts/create-wrappers.js +89 -0
- package/dist/scripts/generate-manifest.js +155 -0
- package/dist/scripts/generate-test-manifest.js +77 -0
- package/dist/scripts/migrate-datetime-to-timestamp.ts +310 -0
- package/dist/scripts/prepack.js +49 -0
- package/dist/signals/bus.d.ts +64 -0
- package/dist/signals/bus.d.ts.map +1 -0
- package/dist/signals/bus.js +102 -0
- package/dist/signals/bus.js.map +1 -0
- package/dist/signals/index.d.ts +11 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/sanitizer.d.ts +54 -0
- package/dist/signals/sanitizer.d.ts.map +1 -0
- package/dist/signals/sanitizer.js +111 -0
- package/dist/signals/sanitizer.js.map +1 -0
- package/dist/smrt-knowledge.json +335 -0
- package/dist/system/compatibility.d.ts +8 -0
- package/dist/system/compatibility.d.ts.map +1 -0
- package/dist/system/compatibility.js +409 -0
- package/dist/system/compatibility.js.map +1 -0
- package/dist/system/index.d.ts +9 -0
- package/dist/system/index.d.ts.map +1 -0
- package/dist/system/schema.d.ts +69 -0
- package/dist/system/schema.d.ts.map +1 -0
- package/dist/system/schema.js +271 -0
- package/dist/system/schema.js.map +1 -0
- package/dist/system/types.d.ts +135 -0
- package/dist/system/types.d.ts.map +1 -0
- package/dist/system-fields.d.ts +44 -0
- package/dist/system-fields.d.ts.map +1 -0
- package/dist/system-fields.js +55 -0
- package/dist/system-fields.js.map +1 -0
- package/dist/table-cache.d.ts +28 -0
- package/dist/table-cache.d.ts.map +1 -0
- package/dist/table-cache.js +21 -0
- package/dist/table-cache.js.map +1 -0
- package/dist/test-utils.d.ts +140 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/testing/database.d.ts +73 -0
- package/dist/testing/database.d.ts.map +1 -0
- package/dist/testing/database.js +204 -0
- package/dist/testing/database.js.map +1 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +5 -0
- package/dist/testing.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/tool-executor.d.ts +101 -0
- package/dist/tools/tool-executor.d.ts.map +1 -0
- package/dist/tools/tool-executor.js +142 -0
- package/dist/tools/tool-executor.js.map +1 -0
- package/dist/tools/tool-generator.d.ts +54 -0
- package/dist/tools/tool-generator.d.ts.map +1 -0
- package/dist/tools/tool-generator.js +121 -0
- package/dist/tools/tool-generator.js.map +1 -0
- package/dist/utils/chunk.d.ts +32 -0
- package/dist/utils/chunk.d.ts.map +1 -0
- package/dist/utils/chunk.js +14 -0
- package/dist/utils/chunk.js.map +1 -0
- package/dist/utils/import-workspace-module.d.ts +8 -0
- package/dist/utils/import-workspace-module.d.ts.map +1 -0
- package/dist/utils/import-workspace-module.js +81 -0
- package/dist/utils/import-workspace-module.js.map +1 -0
- package/dist/utils/json.d.ts +102 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +43 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/lru-cache.d.ts +69 -0
- package/dist/utils/lru-cache.d.ts.map +1 -0
- package/dist/utils/lru-cache.js +100 -0
- package/dist/utils/lru-cache.js.map +1 -0
- package/dist/utils/naming.d.ts +16 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +23 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/utils/qualified-names.d.ts +122 -0
- package/dist/utils/qualified-names.d.ts.map +1 -0
- package/dist/utils/qualified-names.js +82 -0
- package/dist/utils/qualified-names.js.map +1 -0
- package/dist/utils/scanner-module.d.ts +37 -0
- package/dist/utils/scanner-module.d.ts.map +1 -0
- package/dist/utils.d.ts +177 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +185 -0
- package/dist/utils.js.map +1 -0
- package/dist/vite-plugin/import-build-aware.d.ts +68 -0
- package/dist/vite-plugin/import-build-aware.d.ts.map +1 -0
- package/dist/vite-plugin/import-build-aware.js +72 -0
- package/dist/vite-plugin/import-build-aware.js.map +1 -0
- package/dist/vite-plugin/index.d.ts +59 -0
- package/dist/vite-plugin/index.d.ts.map +1 -0
- package/dist/vite-plugin/index.js +1400 -0
- package/dist/vite-plugin/index.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts +66 -0
- package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.js +1375 -0
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -0
- package/dist/vite-plugin/templates/default-ui.ts +432 -0
- package/dist/vite-plugin/templates/default.html +206 -0
- package/dist/vite-plugin.d.ts +8 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +11 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +208 -0
package/dist/registry.js
ADDED
|
@@ -0,0 +1,2301 @@
|
|
|
1
|
+
import { createLogger } from "@happyvertical/logger";
|
|
2
|
+
import { applyOneToManyChildAccessors } from "./child-accessors.js";
|
|
3
|
+
import { SmrtCollection } from "./collection.js";
|
|
4
|
+
import { applyPendingDecoratorRegistrations } from "./decorators/compatibility.js";
|
|
5
|
+
import { ConfigurationError } from "./errors.js";
|
|
6
|
+
import { getDefaultCompositeSource } from "./manifest/sources/composite.js";
|
|
7
|
+
import { ExplicitPathsManifestSource } from "./manifest/sources/explicit-paths.js";
|
|
8
|
+
import { getNodeBuiltins, loadExternalManifestSyncWithNode, cloneManifestSchemaColumns, discoverCachedManifestSync } from "./manifest/store.js";
|
|
9
|
+
import { resolveCollectionCacheConfig } from "./registry/cache-config.js";
|
|
10
|
+
import { register, registerCollection, registerFromManifest, invalidateInheritanceEntries } from "./registry/class-registration.js";
|
|
11
|
+
import { recordRegistryDiagnostic, getRegistryDiagnostics, clearRegistryDiagnostics, flushRegistryDiagnostics } from "./registry/diagnostics.js";
|
|
12
|
+
import { getEmbeddingConfig, hasEmbeddings, getEmbeddingClasses, getProjectEmbeddingConfig, resolveEmbeddingConfig } from "./registry/embedding-manager.js";
|
|
13
|
+
import { getInheritanceChain, getAllFields, mergeFieldConfigs, getAllMethods } from "./registry/inheritance-resolver.js";
|
|
14
|
+
import { manifestFieldDiffers, createFieldFromManifest, mergeManifestField } from "./registry/manifest-field-merge.js";
|
|
15
|
+
import { getCanonicalClassName, hasClassCaseInsensitive, findClass, getClass, getClassByConstructor, getClassByQualifiedName, getClassInPackage, findClassesByName, resolveType, getClassesByPackage, getClassesByVisibility, getPublicClasses, getAllClasses, getClassNames, getQualifiedClassNames, hasClass } from "./registry/name-resolver.js";
|
|
16
|
+
import { getDependencyGraph, getRelationshipMap } from "./registry/relationship-graph.js";
|
|
17
|
+
import { getSchema, getSchemaDDL, getTableName, getAllSchemas, getAllSchemasAsDefinitions } from "./registry/schema-builder.js";
|
|
18
|
+
import { getClasses, getCollections, getCollectionTableNames, getCollectionCache, getDbInstanceIds, getNextDbId, setNextDbId, getFieldDecorators, getStiSiblingsLoaded, getInheritanceCache, getDiscoveryAttemptCache, verboseLog } from "./registry/shared-state.js";
|
|
19
|
+
import { compileValidators } from "./registry/validator.js";
|
|
20
|
+
import "./utils.js";
|
|
21
|
+
import { LRUCache } from "./utils/lru-cache.js";
|
|
22
|
+
import { isQualifiedName, parseQualifiedName, createQualifiedName } from "./utils/qualified-names.js";
|
|
23
|
+
import { SMRT_COLLECTION_BASE_NAMES, isSmrtCollectionExtendsName } from "./registry/collection-resolution.js";
|
|
24
|
+
import { classnameToTablename, toSnakeCase } from "./utils/naming.js";
|
|
25
|
+
const logger = createLogger({ level: "info" });
|
|
26
|
+
function getManifestLoaderSpecifier() {
|
|
27
|
+
return import.meta.url.endsWith(".ts") ? "./manifest/index.ts" : "./manifest/index.js";
|
|
28
|
+
}
|
|
29
|
+
async function importManifestLoader() {
|
|
30
|
+
return await import(getManifestLoaderSpecifier());
|
|
31
|
+
}
|
|
32
|
+
function getReferenceKindFromFieldOptions(fieldOptions) {
|
|
33
|
+
if (fieldOptions?.__tenancy?.isTenantIdField || fieldOptions?._meta?.__tenancy?.isTenantIdField) {
|
|
34
|
+
return "tenantId";
|
|
35
|
+
}
|
|
36
|
+
if (fieldOptions?.type === "foreignKey") {
|
|
37
|
+
return "foreignKey";
|
|
38
|
+
}
|
|
39
|
+
if (fieldOptions?.type === "crossPackageRef") {
|
|
40
|
+
return "crossPackageRef";
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
function applyManifestFieldColumnMetadata(columns, fieldEntries) {
|
|
45
|
+
if (!fieldEntries) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
for (const [fieldName, fieldOptions] of fieldEntries) {
|
|
49
|
+
const sqlType = fieldOptions?.sqlType ?? fieldOptions?._meta?.sqlType;
|
|
50
|
+
const referenceKind = getReferenceKindFromFieldOptions(fieldOptions);
|
|
51
|
+
if (!sqlType && !referenceKind) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const columnName = toSnakeCase(fieldName);
|
|
55
|
+
const existingColumn = columns[columnName];
|
|
56
|
+
if (!existingColumn) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
columns[columnName] = {
|
|
60
|
+
...existingColumn,
|
|
61
|
+
...sqlType ? { type: String(sqlType).toUpperCase() } : {},
|
|
62
|
+
...referenceKind ? { referenceKind } : {}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function discoverInstalledSmrtPackages() {
|
|
67
|
+
const discoverSpecifier = import.meta.url.endsWith(".ts") ? "./manifest/discover-smrt-packages.ts" : "./manifest/discover-smrt-packages.js";
|
|
68
|
+
const { discoverSmrtPackages } = await import(discoverSpecifier);
|
|
69
|
+
return discoverSmrtPackages();
|
|
70
|
+
}
|
|
71
|
+
function resolveManifestByUpwardSearch(builtins, missingPath) {
|
|
72
|
+
const { fs, path } = builtins;
|
|
73
|
+
const fileName = path.basename(missingPath);
|
|
74
|
+
const MAX_LEVELS = 4;
|
|
75
|
+
let dir = path.dirname(missingPath);
|
|
76
|
+
for (let level = 0; level < MAX_LEVELS; level++) {
|
|
77
|
+
const parent = path.dirname(dir);
|
|
78
|
+
if (parent === dir) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
const candidate = path.join(parent, fileName);
|
|
82
|
+
if (fs.existsSync(candidate)) {
|
|
83
|
+
return candidate;
|
|
84
|
+
}
|
|
85
|
+
if (fs.existsSync(path.join(parent, "package.json"))) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
dir = parent;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
class ObjectRegistry {
|
|
93
|
+
/**
|
|
94
|
+
* Get the classes map from globalThis, initializing if needed
|
|
95
|
+
*/
|
|
96
|
+
static get classes() {
|
|
97
|
+
return getClasses();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the collections map from globalThis, initializing if needed
|
|
101
|
+
*/
|
|
102
|
+
static get collections() {
|
|
103
|
+
return getCollections();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the collection table names map from globalThis, initializing if needed
|
|
107
|
+
* Maps collection class name -> tableName for getTableName lookups
|
|
108
|
+
*/
|
|
109
|
+
static get collectionTableNames() {
|
|
110
|
+
return getCollectionTableNames();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Set the table name for a collection class
|
|
114
|
+
* Used by @smrt() decorator to enable getTableName lookups for collections
|
|
115
|
+
*/
|
|
116
|
+
static setCollectionTableName(collectionName, tableName) {
|
|
117
|
+
ObjectRegistry.collectionTableNames.set(collectionName, tableName);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the collection cache from globalThis, initializing if needed
|
|
121
|
+
*/
|
|
122
|
+
static get collectionCache() {
|
|
123
|
+
return getCollectionCache();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Configure the collection cache size (for testing purposes)
|
|
127
|
+
*
|
|
128
|
+
* Resets the collection cache with a new maximum size.
|
|
129
|
+
* Use this in tests to avoid creating many database files.
|
|
130
|
+
*
|
|
131
|
+
* @param maxSize - Maximum number of collections to cache (system default is 100)
|
|
132
|
+
* @throws Error if maxSize is not a positive finite number
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* // In test setup, use a small cache to test LRU eviction with fewer DBs
|
|
136
|
+
* ObjectRegistry.configureCollectionCache(5);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
static configureCollectionCache(maxSize) {
|
|
140
|
+
if (!Number.isFinite(maxSize) || maxSize <= 0) {
|
|
141
|
+
throw new Error(`maxSize must be a positive number, got: ${maxSize}`);
|
|
142
|
+
}
|
|
143
|
+
globalThis.__smrtRegistryCollectionCache = new LRUCache(maxSize);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* WeakMap to assign unique IDs to database instances for cache keys
|
|
147
|
+
* Prevents cache key collisions when different db instances are used
|
|
148
|
+
*/
|
|
149
|
+
static get dbInstanceIds() {
|
|
150
|
+
return getDbInstanceIds();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get/set the next database ID counter
|
|
154
|
+
*/
|
|
155
|
+
static get nextDbId() {
|
|
156
|
+
return getNextDbId();
|
|
157
|
+
}
|
|
158
|
+
static set nextDbId(value) {
|
|
159
|
+
setNextDbId(value);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Storage for field decorator metadata (decorator pattern)
|
|
163
|
+
* Maps className → Map<propertyKey, FieldOptions>
|
|
164
|
+
* Used by @field(), @foreignKey(), @oneToMany(), @manyToMany() decorators
|
|
165
|
+
*/
|
|
166
|
+
static get fieldDecorators() {
|
|
167
|
+
return getFieldDecorators();
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Track collections that have been processed for STI siblings
|
|
171
|
+
* Prevents infinite recursion when loading siblings
|
|
172
|
+
*/
|
|
173
|
+
static get stiSiblingsLoaded() {
|
|
174
|
+
return getStiSiblingsLoaded();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Check if a class is already registered (case-insensitive)
|
|
178
|
+
* Returns the canonical name if found, undefined otherwise.
|
|
179
|
+
*
|
|
180
|
+
* Release B (#1133): iteration over `classes` replaces the old eagerly-
|
|
181
|
+
* maintained classNameMap index. Negligible cost at production scale and
|
|
182
|
+
* removes an entire class of cache-sync bugs.
|
|
183
|
+
*/
|
|
184
|
+
static getCanonicalClassName(name) {
|
|
185
|
+
return getCanonicalClassName(name);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if a class exists by name (case-insensitive)
|
|
189
|
+
*/
|
|
190
|
+
static hasClassCaseInsensitive(name) {
|
|
191
|
+
return hasClassCaseInsensitive(name);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Global cache for inheritance chains (shared across all instances)
|
|
195
|
+
* Maps className → full inheritance chain (base to child)
|
|
196
|
+
* Performance optimization: ~100x faster than re-walking prototype chain
|
|
197
|
+
* Cache size is configurable via smrt.inheritance.cacheSize (default: 200)
|
|
198
|
+
*/
|
|
199
|
+
static getInheritanceCache() {
|
|
200
|
+
return getInheritanceCache();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Global cache for manifest discovery attempts (Issue #735 optimization)
|
|
204
|
+
* Maps className → boolean (true = found and registered, false = not found)
|
|
205
|
+
* Prevents repeated discoverManifestSync calls for the same class name
|
|
206
|
+
*/
|
|
207
|
+
static getDiscoveryAttemptCache() {
|
|
208
|
+
return getDiscoveryAttemptCache();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Register field decorator metadata
|
|
212
|
+
*
|
|
213
|
+
* Called by property decorators (@field, @foreignKey, etc.) to store
|
|
214
|
+
* field configuration metadata. This enables the decorator pattern
|
|
215
|
+
* where field metadata is attached at class definition time.
|
|
216
|
+
*
|
|
217
|
+
* @param className - Name of the class containing the field
|
|
218
|
+
* @param propertyKey - Name of the property being decorated
|
|
219
|
+
* @param options - Field options (type, constraints, etc.)
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Called internally by decorators
|
|
223
|
+
* ObjectRegistry.registerFieldDecorator('Product', 'name', {
|
|
224
|
+
* type: 'text',
|
|
225
|
+
* required: true
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
static registerFieldDecorator(className, propertyKey, options) {
|
|
230
|
+
if (!ObjectRegistry.fieldDecorators.has(className)) {
|
|
231
|
+
ObjectRegistry.fieldDecorators.set(className, /* @__PURE__ */ new Map());
|
|
232
|
+
}
|
|
233
|
+
const classDecorators = ObjectRegistry.fieldDecorators.get(className);
|
|
234
|
+
if (!classDecorators) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const existing = classDecorators.get(propertyKey);
|
|
238
|
+
if (existing) {
|
|
239
|
+
classDecorators.set(propertyKey, { ...existing, ...options });
|
|
240
|
+
} else {
|
|
241
|
+
classDecorators.set(propertyKey, options);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get field decorator metadata for a specific field
|
|
246
|
+
*
|
|
247
|
+
* @param className - Name of the class
|
|
248
|
+
* @param propertyKey - Name of the property
|
|
249
|
+
* @returns Field options or undefined if not decorated
|
|
250
|
+
* @example
|
|
251
|
+
* ```typescript
|
|
252
|
+
* const options = ObjectRegistry.getFieldDecorator('Product', 'name');
|
|
253
|
+
* // { type: 'text', required: true }
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
static getFieldDecorator(className, propertyKey) {
|
|
257
|
+
return ObjectRegistry.fieldDecorators.get(className)?.get(propertyKey);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get all field decorator metadata for a class
|
|
261
|
+
*
|
|
262
|
+
* @param className - Name of the class
|
|
263
|
+
* @returns Map of property names to field options
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const fields = ObjectRegistry.getFieldDecorators('Product');
|
|
267
|
+
* // Map { 'name' => { type: 'text', required: true }, ... }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
static getFieldDecorators(className) {
|
|
271
|
+
return ObjectRegistry.fieldDecorators.get(className) || /* @__PURE__ */ new Map();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check if a class has any field decorators registered
|
|
275
|
+
*
|
|
276
|
+
* @param className - Name of the class
|
|
277
|
+
* @returns True if the class has field decorators, false otherwise
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* if (ObjectRegistry.hasFieldDecorators('Product')) {
|
|
281
|
+
* // Class uses decorators - skip legacy field initialization
|
|
282
|
+
* }
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
static hasFieldDecorators(className) {
|
|
286
|
+
const decorators = ObjectRegistry.fieldDecorators.get(className);
|
|
287
|
+
return decorators !== void 0 && decorators.size > 0;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Register a new SMRT object class with the global registry
|
|
291
|
+
*
|
|
292
|
+
* @param constructor - The class constructor extending SmrtObject
|
|
293
|
+
* @param config - Configuration options for API/CLI/MCP generation
|
|
294
|
+
* @throws {Error} If the class cannot be introspected for field definitions
|
|
295
|
+
* @example
|
|
296
|
+
* ```typescript
|
|
297
|
+
* ObjectRegistry.register(Product, {
|
|
298
|
+
* api: { exclude: ['delete'] },
|
|
299
|
+
* cli: true,
|
|
300
|
+
* mcp: { include: ['list', 'get'] }
|
|
301
|
+
* });
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
static register(ctor, config = {}) {
|
|
305
|
+
register(ctor, config);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Register a collection class for an object
|
|
309
|
+
*
|
|
310
|
+
* @param objectName - Name of the object class this collection manages
|
|
311
|
+
* @param collectionConstructor - The collection class constructor
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* ObjectRegistry.registerCollection('Product', ProductCollection);
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
static registerCollection(objectName, collectionConstructor) {
|
|
318
|
+
registerCollection(objectName, collectionConstructor);
|
|
319
|
+
}
|
|
320
|
+
static registerFromManifest(name, objectDef, packageName) {
|
|
321
|
+
registerFromManifest(name, objectDef, packageName);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Helper method for class lookup with qualified name support.
|
|
325
|
+
*
|
|
326
|
+
* Lookup priority:
|
|
327
|
+
* 1. Direct hit on classes map (works for qualified names as keys)
|
|
328
|
+
* 2. If input contains ':', treat as qualified name — direct only, no fallback
|
|
329
|
+
* 3. classNameMap lookup by simple name (lowercase)
|
|
330
|
+
* - Unambiguous (1 entry) → return it
|
|
331
|
+
* - Ambiguous (>1 entry) → throw descriptive error
|
|
332
|
+
* 4. Case-insensitive iteration fallback (backward compat)
|
|
333
|
+
*
|
|
334
|
+
* @param name - Name of the class to find (simple or qualified)
|
|
335
|
+
* @returns Registered class information or undefined if not found
|
|
336
|
+
* @remarks When a simple name is ambiguous (multiple packages define it),
|
|
337
|
+
* logs a warning and returns the first match. Use qualified names
|
|
338
|
+
* or resolveType() for strict disambiguation.
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
341
|
+
static findClass(name) {
|
|
342
|
+
return findClass(name);
|
|
343
|
+
}
|
|
344
|
+
// The private `findClassStrict` wrapper that used to live here was removed
|
|
345
|
+
// with the inline inheritance-chain copy (#1378); strict, package-aware
|
|
346
|
+
// resolution now lives solely in registry/inheritance-resolver, which calls
|
|
347
|
+
// the name-resolver helper directly.
|
|
348
|
+
/**
|
|
349
|
+
* Get a registered class by name (case-insensitive)
|
|
350
|
+
*
|
|
351
|
+
* @param name - Name of the registered class
|
|
352
|
+
* @returns Registered class information or undefined if not found
|
|
353
|
+
* @example
|
|
354
|
+
* ```typescript
|
|
355
|
+
* const productInfo = ObjectRegistry.getClass('Product');
|
|
356
|
+
* // Also works with: 'product', 'PRODUCT', etc.
|
|
357
|
+
* if (productInfo) {
|
|
358
|
+
* console.log(productInfo.config.api?.exclude);
|
|
359
|
+
* }
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
static getClass(name) {
|
|
363
|
+
return getClass(name);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get a registered class by its constructor reference.
|
|
367
|
+
* This is the most reliable lookup method as it avoids name collision issues
|
|
368
|
+
* that can occur when multiple packages define classes with the same name.
|
|
369
|
+
*
|
|
370
|
+
* Uses a WeakMap index for O(1) lookups.
|
|
371
|
+
*
|
|
372
|
+
* @param ctor - The class constructor to look up
|
|
373
|
+
* @returns Registered class information or undefined if not found
|
|
374
|
+
* @example
|
|
375
|
+
* ```typescript
|
|
376
|
+
* import { Meeting } from '@happyvertical/praeco';
|
|
377
|
+
*
|
|
378
|
+
* const info = ObjectRegistry.getClassByConstructor(Meeting);
|
|
379
|
+
* if (info) {
|
|
380
|
+
* console.log(info.qualifiedName); // '@happyvertical/praeco:Meeting'
|
|
381
|
+
* }
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
static getClassByConstructor(ctor) {
|
|
385
|
+
return getClassByConstructor(ctor);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get a registered class by its qualified name.
|
|
389
|
+
* Qualified names are in format "@package/name:ClassName".
|
|
390
|
+
*
|
|
391
|
+
* @param qualifiedName - The fully qualified class name
|
|
392
|
+
* @returns Registered class information or undefined if not found
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* const product = ObjectRegistry.getClassByQualifiedName('@happyvertical/smrt-products:Product');
|
|
396
|
+
* if (product) {
|
|
397
|
+
* console.log(`Found: ${product.name} from ${product.packageName}`);
|
|
398
|
+
* }
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
static getClassByQualifiedName(qualifiedName) {
|
|
402
|
+
return getClassByQualifiedName(qualifiedName);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Get a registered class by package name and class name.
|
|
406
|
+
* This is a convenience method for looking up classes by their namespace components.
|
|
407
|
+
*
|
|
408
|
+
* @param packageName - The package name (e.g., "@happyvertical/smrt-products")
|
|
409
|
+
* @param className - The class name (e.g., "Product")
|
|
410
|
+
* @returns Registered class information or undefined if not found
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* const product = ObjectRegistry.getClassInPackage('@happyvertical/smrt-products', 'Product');
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
static getClassInPackage(packageName, className) {
|
|
417
|
+
return getClassInPackage(packageName, className);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Find all registered classes with a given simple class name.
|
|
421
|
+
* Useful for detecting collisions or when multiple packages have the same class name.
|
|
422
|
+
*
|
|
423
|
+
* @param className - The simple class name to search for
|
|
424
|
+
* @returns Array of registered classes with matching name
|
|
425
|
+
* @example
|
|
426
|
+
* ```typescript
|
|
427
|
+
* const products = ObjectRegistry.findClassesByName('Product');
|
|
428
|
+
* if (products.length > 1) {
|
|
429
|
+
* console.log(`Found ${products.length} classes named "Product":`);
|
|
430
|
+
* for (const p of products) {
|
|
431
|
+
* console.log(` - ${p.qualifiedName} from ${p.packageName}`);
|
|
432
|
+
* }
|
|
433
|
+
* }
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
static findClassesByName(className) {
|
|
437
|
+
return findClassesByName(className);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Resolve a short class name to its qualified name.
|
|
441
|
+
* Throws an error if the name is ambiguous (multiple packages define the same class name).
|
|
442
|
+
*
|
|
443
|
+
* @param shortName - The simple class name to resolve (e.g., 'MeetingRecap')
|
|
444
|
+
* @returns The qualified class name (e.g., '@happyvertical/praeco:MeetingRecap')
|
|
445
|
+
* @throws {Error} If no class with that name is registered
|
|
446
|
+
* @throws {Error} If multiple classes with that name exist (ambiguous)
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```typescript
|
|
450
|
+
* // Unambiguous resolution
|
|
451
|
+
* const qualified = ObjectRegistry.resolveType('MeetingRecap');
|
|
452
|
+
* // Returns: '@happyvertical/praeco:MeetingRecap'
|
|
453
|
+
*
|
|
454
|
+
* // Already qualified names pass through
|
|
455
|
+
* const same = ObjectRegistry.resolveType('@happyvertical/praeco:MeetingRecap');
|
|
456
|
+
* // Returns: '@happyvertical/praeco:MeetingRecap'
|
|
457
|
+
*
|
|
458
|
+
* // Ambiguous names throw
|
|
459
|
+
* ObjectRegistry.resolveType('Event');
|
|
460
|
+
* // Error: "Event" is ambiguous. Found in multiple packages:
|
|
461
|
+
* // - @happyvertical/smrt-events:Event
|
|
462
|
+
* // - @happyvertical/calendar:Event
|
|
463
|
+
* // Use the fully qualified name instead.
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
static resolveType(shortName) {
|
|
467
|
+
return resolveType(shortName);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Get all registered classes from a specific package.
|
|
471
|
+
*
|
|
472
|
+
* @param packageName - The package name to filter by
|
|
473
|
+
* @returns Map of class names to registered class information
|
|
474
|
+
* @example
|
|
475
|
+
* ```typescript
|
|
476
|
+
* const coreClasses = ObjectRegistry.getClassesByPackage('@happyvertical/smrt-core');
|
|
477
|
+
* for (const [name, info] of coreClasses) {
|
|
478
|
+
* console.log(` ${name}: ${info.qualifiedName}`);
|
|
479
|
+
* }
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
static getClassesByPackage(packageName) {
|
|
483
|
+
return getClassesByPackage(packageName);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Get all registered classes with a specific visibility level.
|
|
487
|
+
*
|
|
488
|
+
* @param visibility - The visibility level to filter by
|
|
489
|
+
* @returns Map of class names to registered class information
|
|
490
|
+
* @example
|
|
491
|
+
* ```typescript
|
|
492
|
+
* const publicClasses = ObjectRegistry.getClassesByVisibility('public');
|
|
493
|
+
* console.log(`Found ${publicClasses.size} public classes`);
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
static getClassesByVisibility(visibility) {
|
|
497
|
+
return getClassesByVisibility(visibility);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Get all public registered classes (excludes 'internal' and 'test' visibility).
|
|
501
|
+
* Useful for generating published manifests or public APIs.
|
|
502
|
+
*
|
|
503
|
+
* @returns Map of class names to registered class information
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* const publicClasses = ObjectRegistry.getPublicClasses();
|
|
507
|
+
* console.log(`Publishing ${publicClasses.size} public classes to manifest`);
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
static getPublicClasses() {
|
|
511
|
+
return getPublicClasses();
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get all registered classes
|
|
515
|
+
*
|
|
516
|
+
* @returns Map of class names to registered class information
|
|
517
|
+
* @example
|
|
518
|
+
* ```typescript
|
|
519
|
+
* const allClasses = ObjectRegistry.getAllClasses();
|
|
520
|
+
* for (const [name, info] of allClasses) {
|
|
521
|
+
* console.log(`Class: ${name}, Fields: ${info.fields.size}`);
|
|
522
|
+
* }
|
|
523
|
+
* ```
|
|
524
|
+
*/
|
|
525
|
+
static getAllClasses() {
|
|
526
|
+
return getAllClasses();
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Get class names, deduplicated by simple class name.
|
|
530
|
+
*
|
|
531
|
+
* This is useful for display and backwards compatibility. Use
|
|
532
|
+
* `getQualifiedClassNames()` for schema/bootstrap loops that must preserve
|
|
533
|
+
* cross-package collisions.
|
|
534
|
+
*/
|
|
535
|
+
static getClassNames() {
|
|
536
|
+
return getClassNames();
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get one lookup name per registered class, preserving cross-package
|
|
540
|
+
* collisions by preferring qualified class names where available.
|
|
541
|
+
*/
|
|
542
|
+
static getQualifiedClassNames() {
|
|
543
|
+
return getQualifiedClassNames();
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Try to load and register a class from external SMRT packages
|
|
547
|
+
*
|
|
548
|
+
* This method attempts to auto-discover classes from @happyvertical/smrt-* packages
|
|
549
|
+
* when they're referenced but not yet registered. Solves issue #343 where STI classes
|
|
550
|
+
* from external packages (e.g., Person from smrt-profiles) weren't loading correctly.
|
|
551
|
+
*
|
|
552
|
+
* @param className - Name of the class to load
|
|
553
|
+
* @returns Promise<boolean> - True if successfully loaded and registered, false otherwise
|
|
554
|
+
* @private
|
|
555
|
+
*/
|
|
556
|
+
static async tryLoadFromExternalPackage(className) {
|
|
557
|
+
const requestedQualifiedName = isQualifiedName(className) ? parseQualifiedName(className) : null;
|
|
558
|
+
const requestedClassName = requestedQualifiedName?.className || className;
|
|
559
|
+
const requestedPackageName = requestedQualifiedName?.packageName;
|
|
560
|
+
const smrtPackages = requestedPackageName ? [requestedPackageName] : await discoverInstalledSmrtPackages();
|
|
561
|
+
const { loadExternalManifest } = await importManifestLoader();
|
|
562
|
+
verboseLog(
|
|
563
|
+
`[ObjectRegistry] Attempting to auto-load ${className} from ${smrtPackages.length} external packages...`
|
|
564
|
+
);
|
|
565
|
+
const matches = [];
|
|
566
|
+
for (const packageName2 of smrtPackages) {
|
|
567
|
+
const manifest2 = await loadExternalManifest(packageName2);
|
|
568
|
+
if (!manifest2 || !manifest2.objects) {
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const lowerClassName = requestedClassName.toLowerCase();
|
|
572
|
+
let objectDef2 = manifest2.objects[lowerClassName] || manifest2.objects[requestedClassName] || (requestedPackageName ? manifest2.objects[`${requestedPackageName}:${requestedClassName}`] : void 0);
|
|
573
|
+
if (!objectDef2) {
|
|
574
|
+
for (const [_key, def] of Object.entries(
|
|
575
|
+
manifest2.objects
|
|
576
|
+
)) {
|
|
577
|
+
if (def.className?.toLowerCase() === lowerClassName || def.className === requestedClassName) {
|
|
578
|
+
objectDef2 = def;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (!objectDef2) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
matches.push({
|
|
587
|
+
manifest: manifest2,
|
|
588
|
+
packageName: manifest2.packageName || packageName2,
|
|
589
|
+
objectDef: objectDef2
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (matches.length === 0) {
|
|
593
|
+
verboseLog(
|
|
594
|
+
`[ObjectRegistry] ❌ Could not find ${className} in any SMRT package`
|
|
595
|
+
);
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
if (!requestedPackageName && matches.length > 1) {
|
|
599
|
+
throw new ConfigurationError(
|
|
600
|
+
`Ambiguous class name "${className}" — found in multiple external SMRT packages: ${matches.map((match) => match.packageName).join(", ")}. Preserve package identity or use a qualified class name to disambiguate.`,
|
|
601
|
+
"CONFIG_AMBIGUOUS_CLASS",
|
|
602
|
+
{
|
|
603
|
+
className,
|
|
604
|
+
candidates: matches.map((match) => match.packageName)
|
|
605
|
+
}
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
const { manifest, packageName, objectDef } = matches[0];
|
|
609
|
+
verboseLog(
|
|
610
|
+
`[ObjectRegistry] ✅ Found ${className} in ${packageName} manifest`
|
|
611
|
+
);
|
|
612
|
+
ObjectRegistry.registerFromManifest(
|
|
613
|
+
objectDef.className || requestedClassName,
|
|
614
|
+
objectDef,
|
|
615
|
+
manifest.packageName
|
|
616
|
+
);
|
|
617
|
+
const childClassName = objectDef.className || requestedClassName;
|
|
618
|
+
if (objectDef.extends && objectDef.extends !== childClassName) {
|
|
619
|
+
const parentName = objectDef.extends;
|
|
620
|
+
const lowerParentName = parentName.toLowerCase();
|
|
621
|
+
let parentDef = manifest.objects[lowerParentName] || manifest.objects[parentName];
|
|
622
|
+
if (!parentDef) {
|
|
623
|
+
for (const [_key, def] of Object.entries(
|
|
624
|
+
manifest.objects
|
|
625
|
+
)) {
|
|
626
|
+
if (def.className?.toLowerCase() === lowerParentName || def.className === parentName) {
|
|
627
|
+
parentDef = def;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (parentDef && !ObjectRegistry.hasClass(objectDef.extends)) {
|
|
633
|
+
verboseLog(
|
|
634
|
+
`[ObjectRegistry] Also registering parent class ${objectDef.extends} for STI`
|
|
635
|
+
);
|
|
636
|
+
ObjectRegistry.registerFromManifest(
|
|
637
|
+
parentDef.className || objectDef.extends,
|
|
638
|
+
parentDef,
|
|
639
|
+
manifest.packageName
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
verboseLog(
|
|
643
|
+
`[ObjectRegistry] Merging inherited fields for ${className}...`
|
|
644
|
+
);
|
|
645
|
+
await ObjectRegistry.getAllFields(requestedClassName);
|
|
646
|
+
const registered = ObjectRegistry.findClass(requestedClassName);
|
|
647
|
+
if (registered?.inheritedFields) {
|
|
648
|
+
verboseLog(
|
|
649
|
+
`[ObjectRegistry] ✅ ${className} now has ${registered.inheritedFields.size} total fields (including inherited)`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Load all manifests upfront so inheritance queries are pure lookups.
|
|
657
|
+
*
|
|
658
|
+
* After this method completes, every class across all packages is
|
|
659
|
+
* registered and `getInheritanceChain()` will never need to mutate
|
|
660
|
+
* registry state.
|
|
661
|
+
*
|
|
662
|
+
* @param options - Optional configuration
|
|
663
|
+
* @param options.manifestPaths - Explicit list of manifest.json file paths to load.
|
|
664
|
+
* When omitted the method discovers packages via `loadExternalManifestSync()`
|
|
665
|
+
* for every `@happyvertical` package found in `node_modules`.
|
|
666
|
+
* @returns Summary statistics about the load operation
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
* ```typescript
|
|
670
|
+
* // At CLI / server startup
|
|
671
|
+
* ObjectRegistry.loadAllManifests();
|
|
672
|
+
* // All classes are now registered — inheritance lookups are side-effect free
|
|
673
|
+
* ```
|
|
674
|
+
*
|
|
675
|
+
* @see https://github.com/happyvertical/smrt/issues/1007
|
|
676
|
+
*/
|
|
677
|
+
static loadAllManifests(options) {
|
|
678
|
+
let packagesLoaded = 0;
|
|
679
|
+
let objectsRegistered = 0;
|
|
680
|
+
if (options?.manifestPaths) {
|
|
681
|
+
const source = new ExplicitPathsManifestSource(options.manifestPaths);
|
|
682
|
+
for (const entry of source.entries()) {
|
|
683
|
+
if (!ObjectRegistry.hasClassCaseInsensitive(entry.className)) {
|
|
684
|
+
ObjectRegistry.registerFromManifest(
|
|
685
|
+
entry.className,
|
|
686
|
+
entry.def,
|
|
687
|
+
entry.packageName
|
|
688
|
+
);
|
|
689
|
+
objectsRegistered++;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
packagesLoaded = source.loadedCount;
|
|
693
|
+
} else {
|
|
694
|
+
const packagePrefixes = ["@happyvertical/smrt-"];
|
|
695
|
+
try {
|
|
696
|
+
const builtins = getNodeBuiltins();
|
|
697
|
+
if (!builtins) {
|
|
698
|
+
return { packagesLoaded, objectsRegistered };
|
|
699
|
+
}
|
|
700
|
+
const scopeDir = builtins.path.join(
|
|
701
|
+
process.cwd(),
|
|
702
|
+
"node_modules",
|
|
703
|
+
"@happyvertical"
|
|
704
|
+
);
|
|
705
|
+
if (!builtins.fs.existsSync(scopeDir)) {
|
|
706
|
+
return { packagesLoaded, objectsRegistered };
|
|
707
|
+
}
|
|
708
|
+
const packages = builtins.fs.readdirSync(scopeDir).filter(
|
|
709
|
+
(name) => packagePrefixes.some(
|
|
710
|
+
(prefix) => `@happyvertical/${name}`.startsWith(prefix)
|
|
711
|
+
)
|
|
712
|
+
);
|
|
713
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
714
|
+
for (const pkgDir of packages) {
|
|
715
|
+
const packageName = `@happyvertical/${pkgDir}`;
|
|
716
|
+
const manifest = loadExternalManifestSyncWithNode(packageName);
|
|
717
|
+
if (!manifest?.objects) continue;
|
|
718
|
+
seenPackages.add(packageName);
|
|
719
|
+
}
|
|
720
|
+
const composite = getDefaultCompositeSource();
|
|
721
|
+
for (const entry of composite.entries()) {
|
|
722
|
+
if (!entry.packageName || !seenPackages.has(entry.packageName)) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (!ObjectRegistry.hasClassCaseInsensitive(entry.className)) {
|
|
726
|
+
ObjectRegistry.registerFromManifest(
|
|
727
|
+
entry.className,
|
|
728
|
+
entry.def,
|
|
729
|
+
entry.packageName
|
|
730
|
+
);
|
|
731
|
+
objectsRegistered++;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
packagesLoaded = seenPackages.size;
|
|
735
|
+
} catch (err) {
|
|
736
|
+
verboseLog(
|
|
737
|
+
`[ObjectRegistry] Auto-discovery of manifests failed: ${err}`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
verboseLog(
|
|
742
|
+
`[ObjectRegistry] loadAllManifests complete: ${packagesLoaded} packages, ${objectsRegistered} objects registered`
|
|
743
|
+
);
|
|
744
|
+
return { packagesLoaded, objectsRegistered };
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Register a package's own bundled manifest, called by the package itself at
|
|
748
|
+
* import time to eliminate the consumer-runtime field-drop bug (issue #1132).
|
|
749
|
+
*
|
|
750
|
+
* Packages ship a `dist/manifest.json` next to their compiled entry. A small
|
|
751
|
+
* `__smrt-register__.js` side-effect module imports that file URL and calls
|
|
752
|
+
* this method *before* any `@smrt()` decorator in the package fires, so the
|
|
753
|
+
* decorator's synchronous manifest lookup hits a populated cache instead of
|
|
754
|
+
* registering classes with zero fields.
|
|
755
|
+
*
|
|
756
|
+
* Silently no-ops if the URL can't be read — in dev/test the vitest plugin
|
|
757
|
+
* populates manifests through a different path, and this fallback is expected
|
|
758
|
+
* to miss in those contexts.
|
|
759
|
+
*
|
|
760
|
+
* @param manifestUrl - File URL (or string) pointing to the package's
|
|
761
|
+
* manifest.json. Typical call site:
|
|
762
|
+
* `new URL('./manifest.json', import.meta.url)`
|
|
763
|
+
* @returns Summary with loaded status, package name, and object count
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```typescript
|
|
767
|
+
* // packages/smrt-places/src/__smrt-register__.ts
|
|
768
|
+
* import { ObjectRegistry } from '@happyvertical/smrt-core';
|
|
769
|
+
* ObjectRegistry.registerPackageManifest(
|
|
770
|
+
* new URL('./manifest.json', import.meta.url),
|
|
771
|
+
* );
|
|
772
|
+
* ```
|
|
773
|
+
*
|
|
774
|
+
* @see https://github.com/happyvertical/smrt/issues/1132
|
|
775
|
+
*/
|
|
776
|
+
static registerPackageManifest(manifestUrl) {
|
|
777
|
+
const builtins = getNodeBuiltins();
|
|
778
|
+
if (!builtins) {
|
|
779
|
+
return { loaded: false, objectsRegistered: 0 };
|
|
780
|
+
}
|
|
781
|
+
let manifest = null;
|
|
782
|
+
try {
|
|
783
|
+
const urlString = manifestUrl instanceof URL ? manifestUrl.href : manifestUrl;
|
|
784
|
+
let filePath;
|
|
785
|
+
if (manifestUrl instanceof URL) {
|
|
786
|
+
filePath = builtins.url.fileURLToPath(manifestUrl);
|
|
787
|
+
} else if (urlString.startsWith("file:")) {
|
|
788
|
+
filePath = builtins.url.fileURLToPath(urlString);
|
|
789
|
+
} else {
|
|
790
|
+
filePath = urlString;
|
|
791
|
+
}
|
|
792
|
+
if (!builtins.fs.existsSync(filePath)) {
|
|
793
|
+
const recovered = resolveManifestByUpwardSearch(builtins, filePath);
|
|
794
|
+
if (!recovered) {
|
|
795
|
+
recordRegistryDiagnostic(
|
|
796
|
+
"warn",
|
|
797
|
+
"PACKAGE_MANIFEST_NOT_FOUND",
|
|
798
|
+
`Package manifest not found at ${filePath}. Self-registration is a no-op; the vitest plugin may still populate this manifest via its own path.`,
|
|
799
|
+
{ manifestUrl: String(manifestUrl), filePath }
|
|
800
|
+
);
|
|
801
|
+
return { loaded: false, objectsRegistered: 0 };
|
|
802
|
+
}
|
|
803
|
+
filePath = recovered;
|
|
804
|
+
}
|
|
805
|
+
const parsed = JSON.parse(
|
|
806
|
+
builtins.fs.readFileSync(filePath, "utf-8")
|
|
807
|
+
);
|
|
808
|
+
if (!parsed?.objects || typeof parsed.objects !== "object") {
|
|
809
|
+
recordRegistryDiagnostic(
|
|
810
|
+
"warn",
|
|
811
|
+
"PACKAGE_MANIFEST_INVALID_SHAPE",
|
|
812
|
+
`Package manifest at ${String(manifestUrl)} is missing an "objects" record`,
|
|
813
|
+
{ manifestUrl: String(manifestUrl) }
|
|
814
|
+
);
|
|
815
|
+
return { loaded: false, objectsRegistered: 0 };
|
|
816
|
+
}
|
|
817
|
+
manifest = parsed;
|
|
818
|
+
} catch (error) {
|
|
819
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
820
|
+
recordRegistryDiagnostic(
|
|
821
|
+
"error",
|
|
822
|
+
"PACKAGE_MANIFEST_READ_FAILED",
|
|
823
|
+
`registerPackageManifest: failed to read manifest at ${String(manifestUrl)}: ${errorMessage}`,
|
|
824
|
+
{ manifestUrl: String(manifestUrl), errorMessage }
|
|
825
|
+
);
|
|
826
|
+
verboseLog(
|
|
827
|
+
`[ObjectRegistry] registerPackageManifest: failed to read manifest at ${String(manifestUrl)}: ${errorMessage}`
|
|
828
|
+
);
|
|
829
|
+
return { loaded: false, objectsRegistered: 0 };
|
|
830
|
+
}
|
|
831
|
+
const packageName = manifest.packageName;
|
|
832
|
+
const manifestGlobals = globalThis;
|
|
833
|
+
if (!manifestGlobals.__smrtManifestCache) {
|
|
834
|
+
manifestGlobals.__smrtManifestCache = /* @__PURE__ */ new Map();
|
|
835
|
+
}
|
|
836
|
+
const cacheKey = packageName || String(manifestUrl);
|
|
837
|
+
manifestGlobals.__smrtManifestCache.set(cacheKey, manifest);
|
|
838
|
+
let objectsRegistered = 0;
|
|
839
|
+
for (const [key, def] of Object.entries(manifest.objects)) {
|
|
840
|
+
const objectDef = def;
|
|
841
|
+
const className = objectDef.className || key;
|
|
842
|
+
ObjectRegistry.registerFromManifest(className, objectDef, packageName);
|
|
843
|
+
objectsRegistered++;
|
|
844
|
+
}
|
|
845
|
+
verboseLog(
|
|
846
|
+
`[ObjectRegistry] registerPackageManifest: loaded ${objectsRegistered} objects from ${packageName || String(manifestUrl)}`
|
|
847
|
+
);
|
|
848
|
+
return { loaded: true, packageName, objectsRegistered };
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Snapshot of diagnostics collected from registry load paths.
|
|
852
|
+
*
|
|
853
|
+
* Registry paths that previously `console.warn(...); return null` also
|
|
854
|
+
* record a structured diagnostic. Apps can inspect this buffer at startup
|
|
855
|
+
* or from an error route to surface failures that would otherwise be silent.
|
|
856
|
+
*
|
|
857
|
+
* Strict mode is **on by default** since Release C (#1134): severity
|
|
858
|
+
* `'error'` diagnostics throw at record time. Set
|
|
859
|
+
* `SMRT_STRICT_REGISTRY=false` to restore the pre-Release-C permissive
|
|
860
|
+
* behavior where only the diagnostic is recorded and nothing throws.
|
|
861
|
+
*
|
|
862
|
+
* @see https://github.com/happyvertical/smrt/issues/1132
|
|
863
|
+
* @see https://github.com/happyvertical/smrt/issues/1134
|
|
864
|
+
*/
|
|
865
|
+
static getDiagnostics() {
|
|
866
|
+
return getRegistryDiagnostics();
|
|
867
|
+
}
|
|
868
|
+
/** Clear the diagnostic buffer. Primarily for tests. */
|
|
869
|
+
static clearDiagnostics() {
|
|
870
|
+
clearRegistryDiagnostics();
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Pretty-print the diagnostic buffer via `console.warn` / `console.error`.
|
|
874
|
+
* No-op when the buffer is empty.
|
|
875
|
+
*/
|
|
876
|
+
static flushDiagnostics() {
|
|
877
|
+
flushRegistryDiagnostics();
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Check if a class is registered (case-insensitive)
|
|
881
|
+
*/
|
|
882
|
+
static hasClass(name) {
|
|
883
|
+
return hasClass(name);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Clear all registered classes (mainly for testing)
|
|
887
|
+
*/
|
|
888
|
+
static clear() {
|
|
889
|
+
ObjectRegistry.classes.clear();
|
|
890
|
+
ObjectRegistry.collections.clear();
|
|
891
|
+
ObjectRegistry.collectionCache.clear();
|
|
892
|
+
ObjectRegistry.collectionTableNames.clear();
|
|
893
|
+
ObjectRegistry.getInheritanceCache().clear();
|
|
894
|
+
ObjectRegistry.getDiscoveryAttemptCache().clear();
|
|
895
|
+
ObjectRegistry.fieldDecorators.clear();
|
|
896
|
+
ObjectRegistry.stiSiblingsLoaded.clear();
|
|
897
|
+
ObjectRegistry.nextDbId = 1;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Invalidate inheritance cache for a specific class
|
|
901
|
+
*
|
|
902
|
+
* Clears cached inheritance chain and merged fields/methods for the given class.
|
|
903
|
+
* Call this when a class definition changes at runtime (e.g., hot module reload).
|
|
904
|
+
*
|
|
905
|
+
* @param className - The class name to invalidate cache for
|
|
906
|
+
* @example
|
|
907
|
+
* ```typescript
|
|
908
|
+
* // After hot module reload of a parent class
|
|
909
|
+
* ObjectRegistry.invalidateInheritanceCache('BentleyContent');
|
|
910
|
+
* ```
|
|
911
|
+
*/
|
|
912
|
+
static invalidateInheritanceCache(className) {
|
|
913
|
+
const registered = ObjectRegistry.findClass(className);
|
|
914
|
+
if (!registered) {
|
|
915
|
+
ObjectRegistry.getInheritanceCache().delete(className);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
invalidateInheritanceEntries(registered);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Invalidate all inheritance caches
|
|
922
|
+
*
|
|
923
|
+
* Clears all cached inheritance chains and merged fields/methods.
|
|
924
|
+
* Call this when multiple classes change at runtime.
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* ```typescript
|
|
928
|
+
* // After hot module reload of multiple classes
|
|
929
|
+
* ObjectRegistry.invalidateAllInheritanceCaches();
|
|
930
|
+
* ```
|
|
931
|
+
*/
|
|
932
|
+
static invalidateAllInheritanceCaches() {
|
|
933
|
+
ObjectRegistry.getInheritanceCache().clear();
|
|
934
|
+
ObjectRegistry.getDiscoveryAttemptCache().clear();
|
|
935
|
+
for (const registered of ObjectRegistry.classes.values()) {
|
|
936
|
+
registered.inheritedFields = void 0;
|
|
937
|
+
registered.inheritedMethods = void 0;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Resolve a collection lookup name to the registered object metadata and
|
|
942
|
+
* collection constructor that should back it.
|
|
943
|
+
*
|
|
944
|
+
* Accepts any supported collection identifier:
|
|
945
|
+
* - simple class name (`FactRecord`)
|
|
946
|
+
* - qualified class name (`@pkg:FactRecord`)
|
|
947
|
+
* - explicit collection alias (`fact_records`) when registered
|
|
948
|
+
*
|
|
949
|
+
* Returns a canonical class identity so cache keys remain stable across
|
|
950
|
+
* aliases that refer to the same underlying collection.
|
|
951
|
+
*/
|
|
952
|
+
static resolveCollectionRegistration(className) {
|
|
953
|
+
let registered = ObjectRegistry.findClass(className);
|
|
954
|
+
let collectionConstructor = registered?.collectionConstructor;
|
|
955
|
+
if (!collectionConstructor) {
|
|
956
|
+
collectionConstructor = ObjectRegistry.collections.get(className);
|
|
957
|
+
}
|
|
958
|
+
if (!registered && collectionConstructor) {
|
|
959
|
+
const itemClass = collectionConstructor._itemClass;
|
|
960
|
+
if (itemClass) {
|
|
961
|
+
registered = ObjectRegistry.getClassByConstructor(itemClass) || ObjectRegistry.findClass(itemClass.name);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const canonicalName = registered?.qualifiedName || registered?.name || className;
|
|
965
|
+
return {
|
|
966
|
+
canonicalName,
|
|
967
|
+
registered,
|
|
968
|
+
collectionConstructor
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Get or create a cached collection instance (Singleton pattern - Phase 4 optimization)
|
|
973
|
+
*
|
|
974
|
+
* Returns a cached collection if one exists for the given class and options,
|
|
975
|
+
* otherwise creates, initializes, and caches a new instance. This significantly
|
|
976
|
+
* improves performance by avoiding repeated collection initialization.
|
|
977
|
+
*
|
|
978
|
+
* **Performance Impact**: 60-80% reduction in collection initialization overhead
|
|
979
|
+
*
|
|
980
|
+
* **Cache Key Strategy**: Collections are cached based on:
|
|
981
|
+
* - className
|
|
982
|
+
* - persistence configuration (type, url, baseUrl)
|
|
983
|
+
* - db presence (not full config)
|
|
984
|
+
* - ai presence (not full config)
|
|
985
|
+
*
|
|
986
|
+
* Different persistence configurations create separate cached instances.
|
|
987
|
+
*
|
|
988
|
+
* @param className - Name of the object class
|
|
989
|
+
* @param options - Configuration options for the collection
|
|
990
|
+
* @returns Cached or newly created collection instance
|
|
991
|
+
* @throws {Error} If the class is not registered or has no collection
|
|
992
|
+
*
|
|
993
|
+
* @example
|
|
994
|
+
* ```typescript
|
|
995
|
+
* // First call creates and caches the collection
|
|
996
|
+
* const orders1 = await ObjectRegistry.getCollection('Order', {
|
|
997
|
+
* persistence: { type: 'sql', url: 'orders.db' }
|
|
998
|
+
* });
|
|
999
|
+
*
|
|
1000
|
+
* // Subsequent calls return the cached instance (much faster)
|
|
1001
|
+
* const orders2 = await ObjectRegistry.getCollection('Order', {
|
|
1002
|
+
* persistence: { type: 'sql', url: 'orders.db' }
|
|
1003
|
+
* });
|
|
1004
|
+
* console.log(orders1 === orders2); // true (same instance)
|
|
1005
|
+
*
|
|
1006
|
+
* // Different configuration creates new instance
|
|
1007
|
+
* const orders3 = await ObjectRegistry.getCollection('Order', {
|
|
1008
|
+
* persistence: { type: 'sql', url: 'orders-copy.db' }
|
|
1009
|
+
* });
|
|
1010
|
+
* console.log(orders1 === orders3); // false (different config)
|
|
1011
|
+
* ```
|
|
1012
|
+
*
|
|
1013
|
+
* @see {@link https://github.com/happyvertical/sdk/blob/main/packages/core/CLAUDE.md#singleton-collection-management-phase-4|Phase 4 Documentation}
|
|
1014
|
+
*/
|
|
1015
|
+
static async getCollection(className, options = {}) {
|
|
1016
|
+
let { canonicalName, registered, collectionConstructor } = ObjectRegistry.resolveCollectionRegistration(className);
|
|
1017
|
+
if (!registered && !collectionConstructor) {
|
|
1018
|
+
const loaded = await ObjectRegistry.tryLoadFromExternalPackage(className);
|
|
1019
|
+
if (loaded) {
|
|
1020
|
+
({ canonicalName, registered, collectionConstructor } = ObjectRegistry.resolveCollectionRegistration(className));
|
|
1021
|
+
}
|
|
1022
|
+
if (!registered && !collectionConstructor) {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`Class ${className} not found in ObjectRegistry. Make sure to register it with @smrt() decorator or ObjectRegistry.register()`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
let dbId;
|
|
1029
|
+
if (options.db && typeof options.db === "object") {
|
|
1030
|
+
if (!ObjectRegistry.dbInstanceIds.has(options.db)) {
|
|
1031
|
+
ObjectRegistry.dbInstanceIds.set(options.db, ObjectRegistry.nextDbId++);
|
|
1032
|
+
}
|
|
1033
|
+
dbId = ObjectRegistry.dbInstanceIds.get(options.db);
|
|
1034
|
+
}
|
|
1035
|
+
const cacheKey = `${canonicalName}:${JSON.stringify({
|
|
1036
|
+
persistence: options.persistence,
|
|
1037
|
+
db: dbId !== void 0 ? `db:${dbId}` : void 0,
|
|
1038
|
+
ai: options.ai ? "present" : void 0
|
|
1039
|
+
})}`;
|
|
1040
|
+
if (ObjectRegistry.collectionCache.has(cacheKey)) {
|
|
1041
|
+
return ObjectRegistry.collectionCache.get(cacheKey);
|
|
1042
|
+
}
|
|
1043
|
+
if (!collectionConstructor) {
|
|
1044
|
+
if (!registered) {
|
|
1045
|
+
throw new Error(
|
|
1046
|
+
`Collection ${className} is registered without a backing object class, which is not supported.`
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
const { SmrtCollection: SmrtCollectionClass } = await import("./collection.js");
|
|
1050
|
+
class DefaultCollection extends SmrtCollectionClass {
|
|
1051
|
+
static _itemClass = registered?.constructor;
|
|
1052
|
+
}
|
|
1053
|
+
collectionConstructor = DefaultCollection;
|
|
1054
|
+
registered.collectionConstructor = DefaultCollection;
|
|
1055
|
+
ObjectRegistry.collections.set(canonicalName, DefaultCollection);
|
|
1056
|
+
}
|
|
1057
|
+
const collection = await collectionConstructor.create(
|
|
1058
|
+
options
|
|
1059
|
+
);
|
|
1060
|
+
ObjectRegistry.collectionCache.set(cacheKey, collection);
|
|
1061
|
+
return collection;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Get field definitions for a registered class.
|
|
1065
|
+
* Supports both qualified names and simple class names.
|
|
1066
|
+
*/
|
|
1067
|
+
static getFields(name) {
|
|
1068
|
+
const registered = ObjectRegistry.findClass(name);
|
|
1069
|
+
return registered ? registered.fields : /* @__PURE__ */ new Map();
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Get method definitions for a registered class
|
|
1073
|
+
*
|
|
1074
|
+
* Returns method metadata extracted from the manifest during AST scanning.
|
|
1075
|
+
* This enables code generators (CLI, API, MCP) to discover custom methods
|
|
1076
|
+
* and automatically generate corresponding commands/endpoints/tools.
|
|
1077
|
+
*
|
|
1078
|
+
* @param name - Name of the registered class
|
|
1079
|
+
* @returns Map of method names to MethodDefinition objects
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```typescript
|
|
1082
|
+
* const methods = ObjectRegistry.getMethods('Agent');
|
|
1083
|
+
* for (const [name, methodDef] of methods) {
|
|
1084
|
+
* console.log(`Method: ${name}`);
|
|
1085
|
+
* console.log(` Async: ${methodDef.async}`);
|
|
1086
|
+
* console.log(` Public: ${methodDef.isPublic}`);
|
|
1087
|
+
* console.log(` Params: ${methodDef.parameters.map(p => p.name).join(', ')}`);
|
|
1088
|
+
* }
|
|
1089
|
+
* ```
|
|
1090
|
+
*/
|
|
1091
|
+
static getMethods(name) {
|
|
1092
|
+
const registered = ObjectRegistry.findClass(name);
|
|
1093
|
+
if (registered) {
|
|
1094
|
+
return registered.methods;
|
|
1095
|
+
}
|
|
1096
|
+
return /* @__PURE__ */ new Map();
|
|
1097
|
+
}
|
|
1098
|
+
static compileValidators(className, fields) {
|
|
1099
|
+
return compileValidators(className, fields);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Ensure manifest is loaded for external package classes
|
|
1103
|
+
*
|
|
1104
|
+
* For classes from external packages, the manifest may not be loaded during
|
|
1105
|
+
* initial registration (which must be synchronous for decorator support).
|
|
1106
|
+
* This method loads the manifest asynchronously when needed.
|
|
1107
|
+
*
|
|
1108
|
+
* @param className - Name of the class to ensure manifest is loaded for
|
|
1109
|
+
* @returns Promise that resolves when manifest is loaded (or already loaded)
|
|
1110
|
+
* @throws {Error} If manifest cannot be found
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* ```typescript
|
|
1114
|
+
* // Before using fields from external package
|
|
1115
|
+
* await ObjectRegistry.ensureManifestLoaded('Place');
|
|
1116
|
+
* const fields = ObjectRegistry.getFields('Place'); // Now has fields
|
|
1117
|
+
* ```
|
|
1118
|
+
*/
|
|
1119
|
+
static async ensureManifestLoaded(className) {
|
|
1120
|
+
let registered = ObjectRegistry.findClass(className);
|
|
1121
|
+
if (!registered) {
|
|
1122
|
+
const loaded = await ObjectRegistry.tryLoadFromExternalPackage(className);
|
|
1123
|
+
if (loaded) {
|
|
1124
|
+
registered = ObjectRegistry.findClass(className);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (!registered) {
|
|
1128
|
+
const isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || typeof globalThis.describe !== "undefined" || typeof globalThis.it !== "undefined";
|
|
1129
|
+
const testHint = isTestEnv ? `
|
|
1130
|
+
|
|
1131
|
+
⚠️ Are you using 'smrt test'? Tests require manifest generation.
|
|
1132
|
+
✅ Use: smrt test
|
|
1133
|
+
❌ NOT: npx vitest
|
|
1134
|
+
` : "";
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`Class '${className}' is not registered. Ensure the class is decorated with @smrt() before using it.` + testHint
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const existingFieldCount = registered.fields.size;
|
|
1140
|
+
const { discoverManifestEntry } = await importManifestLoader();
|
|
1141
|
+
const manifestEntry = await discoverManifestEntry(
|
|
1142
|
+
registered.constructor,
|
|
1143
|
+
className
|
|
1144
|
+
);
|
|
1145
|
+
if (!manifestEntry) {
|
|
1146
|
+
if (!registered.packageName || !registered.qualifiedName) {
|
|
1147
|
+
const loaded = await ObjectRegistry.tryLoadFromExternalPackage(className);
|
|
1148
|
+
if (loaded) {
|
|
1149
|
+
ObjectRegistry.invalidateInheritanceCache(className);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (manifestEntry?.fields) {
|
|
1155
|
+
const manifestFieldCount = Object.keys(manifestEntry.fields).length;
|
|
1156
|
+
let didHydrate = false;
|
|
1157
|
+
const needsFieldHydration = Object.entries(manifestEntry.fields).some(
|
|
1158
|
+
([fieldName, fieldDef]) => {
|
|
1159
|
+
const existingField = registered.fields.get(fieldName);
|
|
1160
|
+
if (!existingField) {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
return manifestFieldDiffers(existingField, fieldDef);
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
const needsMethodHydration = !!manifestEntry.methods && Object.entries(manifestEntry.methods).some(
|
|
1167
|
+
([methodName, methodDef]) => {
|
|
1168
|
+
return JSON.stringify(registered.methods.get(methodName)) !== JSON.stringify(methodDef);
|
|
1169
|
+
}
|
|
1170
|
+
);
|
|
1171
|
+
const needsRegistrationHydration = !!manifestEntry.packageName && registered.packageName !== manifestEntry.packageName || !!manifestEntry.extends && registered.extends !== manifestEntry.extends;
|
|
1172
|
+
if (existingFieldCount > 0 && existingFieldCount >= manifestFieldCount && !needsFieldHydration && !needsMethodHydration && !needsRegistrationHydration) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
for (const [fieldName, fieldDef] of Object.entries(
|
|
1176
|
+
manifestEntry.fields
|
|
1177
|
+
)) {
|
|
1178
|
+
const existingField = registered.fields.get(fieldName);
|
|
1179
|
+
if (!existingField) {
|
|
1180
|
+
registered.fields.set(fieldName, createFieldFromManifest(fieldDef));
|
|
1181
|
+
didHydrate = true;
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (manifestFieldDiffers(existingField, fieldDef)) {
|
|
1185
|
+
registered.fields.set(
|
|
1186
|
+
fieldName,
|
|
1187
|
+
mergeManifestField(existingField, fieldDef)
|
|
1188
|
+
);
|
|
1189
|
+
didHydrate = true;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (manifestEntry.methods) {
|
|
1193
|
+
for (const [methodName, methodDef] of Object.entries(
|
|
1194
|
+
manifestEntry.methods
|
|
1195
|
+
)) {
|
|
1196
|
+
registered.methods.set(methodName, methodDef);
|
|
1197
|
+
}
|
|
1198
|
+
didHydrate = true;
|
|
1199
|
+
}
|
|
1200
|
+
if (manifestEntry.schema) {
|
|
1201
|
+
const columns = cloneManifestSchemaColumns(
|
|
1202
|
+
manifestEntry.schema.columns
|
|
1203
|
+
);
|
|
1204
|
+
applyManifestFieldColumnMetadata(columns, registered.fields.entries());
|
|
1205
|
+
registered.schema = {
|
|
1206
|
+
ddl: manifestEntry.schema.ddl,
|
|
1207
|
+
tableName: manifestEntry.schema.tableName || registered.schema?.tableName || classnameToTablename(registered.name),
|
|
1208
|
+
columns,
|
|
1209
|
+
indexes: manifestEntry.schema.indexes?.map((indexDef) => ({
|
|
1210
|
+
...indexDef,
|
|
1211
|
+
columns: [...indexDef.columns || []]
|
|
1212
|
+
})) || [],
|
|
1213
|
+
triggers: [],
|
|
1214
|
+
foreignKeys: [],
|
|
1215
|
+
dependencies: [],
|
|
1216
|
+
version: manifestEntry.schema.version || "",
|
|
1217
|
+
packageName: manifestEntry.packageName,
|
|
1218
|
+
baseClass: registered.schema?.baseClass
|
|
1219
|
+
};
|
|
1220
|
+
didHydrate = true;
|
|
1221
|
+
}
|
|
1222
|
+
if (manifestEntry.packageName) {
|
|
1223
|
+
registered.packageName = manifestEntry.packageName;
|
|
1224
|
+
registered.qualifiedName = createQualifiedName(
|
|
1225
|
+
manifestEntry.packageName,
|
|
1226
|
+
registered.name
|
|
1227
|
+
);
|
|
1228
|
+
didHydrate = true;
|
|
1229
|
+
}
|
|
1230
|
+
if (manifestEntry.extends) {
|
|
1231
|
+
registered.extends = manifestEntry.extends;
|
|
1232
|
+
didHydrate = true;
|
|
1233
|
+
}
|
|
1234
|
+
if (didHydrate) {
|
|
1235
|
+
ObjectRegistry.invalidateInheritanceCache(className);
|
|
1236
|
+
}
|
|
1237
|
+
verboseLog(
|
|
1238
|
+
`📦 Loaded manifest for external package class: ${className} (${registered.fields.size} fields, ${registered.methods.size} methods)`
|
|
1239
|
+
);
|
|
1240
|
+
} else {
|
|
1241
|
+
throw new Error(
|
|
1242
|
+
`Cannot find manifest for class '${className}'.
|
|
1243
|
+
|
|
1244
|
+
SMRT classes require a manifest generated at build time by the AST scanner.
|
|
1245
|
+
|
|
1246
|
+
If this is an external package class, verify:
|
|
1247
|
+
1. Package exports manifest: Check package.json has:
|
|
1248
|
+
"exports": { "./manifest": "./dist/manifest.json" }
|
|
1249
|
+
2. Package is built: Run npm run build in the package
|
|
1250
|
+
3. Manifest exists: Confirm dist/manifest.json exists
|
|
1251
|
+
|
|
1252
|
+
See: https://github.com/happyvertical/smrt/issues/131`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Get configuration for a registered class
|
|
1258
|
+
*/
|
|
1259
|
+
static getConfig(name) {
|
|
1260
|
+
const registered = ObjectRegistry.findClass(name);
|
|
1261
|
+
return registered ? registered.config : {};
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Get cached schema definition for a registered class
|
|
1265
|
+
*/
|
|
1266
|
+
static getSchema(name) {
|
|
1267
|
+
return getSchema(name);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Get SQL DDL statement for a registered class
|
|
1271
|
+
*/
|
|
1272
|
+
static getSchemaDDL(name) {
|
|
1273
|
+
return getSchemaDDL(name);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Get table name for a registered class
|
|
1277
|
+
*/
|
|
1278
|
+
static getTableName(name) {
|
|
1279
|
+
return getTableName(name);
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Get all pre-generated schemas for passing to database adapters
|
|
1283
|
+
*/
|
|
1284
|
+
static getAllSchemas() {
|
|
1285
|
+
return getAllSchemas();
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Get all registered schemas as SchemaDefinition objects
|
|
1289
|
+
*/
|
|
1290
|
+
static getAllSchemasAsDefinitions() {
|
|
1291
|
+
return getAllSchemasAsDefinitions();
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Get compiled validation functions for a registered class
|
|
1295
|
+
*
|
|
1296
|
+
* Returns pre-compiled validation functions that can be executed
|
|
1297
|
+
* at runtime for efficient validation without repeated setup.
|
|
1298
|
+
*
|
|
1299
|
+
* @param name - Name of the registered class
|
|
1300
|
+
* @returns Array of validation functions or undefined if not found
|
|
1301
|
+
* @example
|
|
1302
|
+
* ```typescript
|
|
1303
|
+
* const validators = ObjectRegistry.getValidators('Product');
|
|
1304
|
+
* for (const validator of validators || []) {
|
|
1305
|
+
* const error = await validator(productInstance);
|
|
1306
|
+
* if (error) console.error(error);
|
|
1307
|
+
* }
|
|
1308
|
+
* ```
|
|
1309
|
+
*/
|
|
1310
|
+
static getValidators(name) {
|
|
1311
|
+
const registered = ObjectRegistry.findClass(name);
|
|
1312
|
+
return registered?.validators;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Get pre-computed validation rules for a registered class
|
|
1316
|
+
*
|
|
1317
|
+
* Returns serializable validation rules that can be evaluated at runtime
|
|
1318
|
+
* without needing pre-compiled validator closures. This significantly
|
|
1319
|
+
* reduces CLI startup time for projects with many SMRT objects.
|
|
1320
|
+
*
|
|
1321
|
+
* @param name - Name of the registered class
|
|
1322
|
+
* @returns Array of validation rules or undefined if not found
|
|
1323
|
+
* @see https://github.com/happyvertical/smrt/issues/782
|
|
1324
|
+
*/
|
|
1325
|
+
static getValidationRules(name) {
|
|
1326
|
+
const registered = ObjectRegistry.findClass(name);
|
|
1327
|
+
return registered?.validationRules;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Validate an instance using pre-computed validation rules
|
|
1331
|
+
*
|
|
1332
|
+
* This method evaluates validation rules without creating closures,
|
|
1333
|
+
* providing faster validation than compiled validator functions.
|
|
1334
|
+
*
|
|
1335
|
+
* @param instance - The object instance to validate
|
|
1336
|
+
* @param rules - Array of validation rules to apply
|
|
1337
|
+
* @param className - Name of the class for error messages
|
|
1338
|
+
* @returns Array of validation errors (empty if valid)
|
|
1339
|
+
* @see https://github.com/happyvertical/smrt/issues/782
|
|
1340
|
+
*/
|
|
1341
|
+
static async validateWithRules(instance, rules, className) {
|
|
1342
|
+
const { ValidationError } = await import("./errors.js");
|
|
1343
|
+
const errors = [];
|
|
1344
|
+
for (const rule of rules) {
|
|
1345
|
+
const value = instance[rule.field];
|
|
1346
|
+
switch (rule.rule) {
|
|
1347
|
+
case "required":
|
|
1348
|
+
if (value === null || value === void 0 || value === "") {
|
|
1349
|
+
errors.push(ValidationError.requiredField(rule.field, className));
|
|
1350
|
+
}
|
|
1351
|
+
break;
|
|
1352
|
+
case "min":
|
|
1353
|
+
if (value !== null && value !== void 0 && value < rule.value) {
|
|
1354
|
+
errors.push(
|
|
1355
|
+
ValidationError.rangeError(
|
|
1356
|
+
rule.field,
|
|
1357
|
+
value,
|
|
1358
|
+
rule.value,
|
|
1359
|
+
void 0
|
|
1360
|
+
)
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
break;
|
|
1364
|
+
case "max":
|
|
1365
|
+
if (value !== null && value !== void 0 && value > rule.value) {
|
|
1366
|
+
errors.push(
|
|
1367
|
+
ValidationError.rangeError(
|
|
1368
|
+
rule.field,
|
|
1369
|
+
value,
|
|
1370
|
+
void 0,
|
|
1371
|
+
rule.value
|
|
1372
|
+
)
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
break;
|
|
1376
|
+
case "minLength":
|
|
1377
|
+
if (value && typeof value === "string" && value.length < rule.value) {
|
|
1378
|
+
errors.push(
|
|
1379
|
+
ValidationError.invalidValue(
|
|
1380
|
+
rule.field,
|
|
1381
|
+
value,
|
|
1382
|
+
`string with minimum length ${rule.value}`
|
|
1383
|
+
)
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
break;
|
|
1387
|
+
case "maxLength":
|
|
1388
|
+
if (value && typeof value === "string" && value.length > rule.value) {
|
|
1389
|
+
errors.push(
|
|
1390
|
+
ValidationError.invalidValue(
|
|
1391
|
+
rule.field,
|
|
1392
|
+
value,
|
|
1393
|
+
`string with maximum length ${rule.value}`
|
|
1394
|
+
)
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
break;
|
|
1398
|
+
case "pattern":
|
|
1399
|
+
if (value && typeof value === "string") {
|
|
1400
|
+
const regex = new RegExp(rule.value);
|
|
1401
|
+
if (!regex.test(value)) {
|
|
1402
|
+
errors.push(
|
|
1403
|
+
ValidationError.invalidValue(
|
|
1404
|
+
rule.field,
|
|
1405
|
+
value,
|
|
1406
|
+
`string matching pattern ${rule.value}`
|
|
1407
|
+
)
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
break;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return errors;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Build dependency graph from foreignKey relationships
|
|
1418
|
+
*
|
|
1419
|
+
* Returns a map where keys are class names and values are arrays
|
|
1420
|
+
* of class names that the key depends on (via foreignKey fields).
|
|
1421
|
+
*
|
|
1422
|
+
* @returns Map of class name to array of dependency class names
|
|
1423
|
+
* @example
|
|
1424
|
+
* ```typescript
|
|
1425
|
+
* const deps = ObjectRegistry.getDependencyGraph();
|
|
1426
|
+
* // { 'Order': ['Customer', 'Product'], 'Customer': [], 'Product': ['Category'] }
|
|
1427
|
+
* ```
|
|
1428
|
+
*/
|
|
1429
|
+
static getDependencyGraph() {
|
|
1430
|
+
return getDependencyGraph();
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Get initialization order for classes based on dependency graph
|
|
1434
|
+
*
|
|
1435
|
+
* Uses topological sort to ensure that classes are initialized in
|
|
1436
|
+
* an order that respects foreignKey dependencies (dependencies first).
|
|
1437
|
+
*
|
|
1438
|
+
* Foreign-key cycles (e.g. smrt-chat's ChatMessage.threadId -> ChatThread
|
|
1439
|
+
* and ChatThread.rootMessageId -> ChatMessage) are BROKEN rather than
|
|
1440
|
+
* treated as a fatal error: when a back-edge is encountered the recursion
|
|
1441
|
+
* stops there, so every class still appears in the returned order. SMRT does
|
|
1442
|
+
* not emit real DB `FOREIGN KEY` constraints in its generated CREATE TABLE
|
|
1443
|
+
* DDL, so a table can be created before its cyclic reference target exists —
|
|
1444
|
+
* the reference is satisfied once both tables are present. This mirrors the
|
|
1445
|
+
* standard RDBMS approach (create tables first, wire cyclic references
|
|
1446
|
+
* afterward) and matches SchemaManager.sortByDependencies, which already
|
|
1447
|
+
* tolerates cycles. See issue #1333.
|
|
1448
|
+
*
|
|
1449
|
+
* @returns Array of class names in initialization order. Every registered
|
|
1450
|
+
* class appears exactly once; cycle back-edges are dropped from the
|
|
1451
|
+
* ordering constraints.
|
|
1452
|
+
* @example
|
|
1453
|
+
* ```typescript
|
|
1454
|
+
* const order = ObjectRegistry.getInitializationOrder();
|
|
1455
|
+
* // ['Category', 'Product', 'Customer', 'Order']
|
|
1456
|
+
* // Tables are created in this order to avoid foreign key errors
|
|
1457
|
+
* ```
|
|
1458
|
+
*/
|
|
1459
|
+
static getInitializationOrder() {
|
|
1460
|
+
const graph = ObjectRegistry.getDependencyGraph();
|
|
1461
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1462
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1463
|
+
const order = [];
|
|
1464
|
+
const cycleMembers = /* @__PURE__ */ new Set();
|
|
1465
|
+
function visit(className) {
|
|
1466
|
+
if (visiting.has(className)) {
|
|
1467
|
+
cycleMembers.add(className);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (visited.has(className)) {
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
visiting.add(className);
|
|
1474
|
+
const dependencies = graph.get(className) || [];
|
|
1475
|
+
for (const dep of dependencies) {
|
|
1476
|
+
visit(dep);
|
|
1477
|
+
}
|
|
1478
|
+
visiting.delete(className);
|
|
1479
|
+
visited.add(className);
|
|
1480
|
+
order.push(className);
|
|
1481
|
+
}
|
|
1482
|
+
for (const className of graph.keys()) {
|
|
1483
|
+
if (!visited.has(className)) {
|
|
1484
|
+
visit(className);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
if (cycleMembers.size > 0) {
|
|
1488
|
+
logger.warn(
|
|
1489
|
+
`[ObjectRegistry] Foreign-key cycle(s) detected and broken for ordering: ${[
|
|
1490
|
+
...cycleMembers
|
|
1491
|
+
].join(", ")}. Tables are created without strict cyclic ordering; SMRT does not emit DB-level FOREIGN KEY constraints, so this is safe.`
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
return order;
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Build comprehensive relationship map from all field types
|
|
1498
|
+
*
|
|
1499
|
+
* Returns a map containing all relationships (foreignKey, oneToMany, manyToMany)
|
|
1500
|
+
* discovered in registered classes. This enables runtime relationship traversal
|
|
1501
|
+
* and eager/lazy loading of related objects.
|
|
1502
|
+
*
|
|
1503
|
+
* @returns Map of class name to array of relationship metadata
|
|
1504
|
+
* @example
|
|
1505
|
+
* ```typescript
|
|
1506
|
+
* const relationships = ObjectRegistry.getRelationshipMap();
|
|
1507
|
+
* // {
|
|
1508
|
+
* // 'Order': [
|
|
1509
|
+
* // { sourceClass: 'Order', fieldName: 'customerId', targetClass: 'Customer',
|
|
1510
|
+
* // type: 'foreignKey', options: { onDelete: 'restrict' } }
|
|
1511
|
+
* // ],
|
|
1512
|
+
* // 'Customer': [
|
|
1513
|
+
* // { sourceClass: 'Customer', fieldName: 'orders', targetClass: 'Order',
|
|
1514
|
+
* // type: 'oneToMany', options: {} }
|
|
1515
|
+
* // ]
|
|
1516
|
+
* // }
|
|
1517
|
+
* ```
|
|
1518
|
+
*/
|
|
1519
|
+
static getRelationshipMap() {
|
|
1520
|
+
return getRelationshipMap();
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Get relationships for a specific class
|
|
1524
|
+
*
|
|
1525
|
+
* @param className - Name of the class to get relationships for
|
|
1526
|
+
* @returns Array of relationship metadata for the class
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```typescript
|
|
1529
|
+
* const orderRelationships = ObjectRegistry.getRelationships('Order');
|
|
1530
|
+
* // [{ sourceClass: 'Order', fieldName: 'customerId', ... }]
|
|
1531
|
+
* ```
|
|
1532
|
+
*/
|
|
1533
|
+
static getRelationships(className) {
|
|
1534
|
+
return ObjectRegistry.getRelationshipMap().get(className) || [];
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Get full inheritance chain for a class
|
|
1538
|
+
*
|
|
1539
|
+
* Returns array of registered ancestor class names from the oldest
|
|
1540
|
+
* registered ancestor down to the input class. Framework base classes
|
|
1541
|
+
* (`SmrtObject`, `SmrtClass`, `SmrtCollection`) are **NOT** included
|
|
1542
|
+
* because they are never registered — the walk terminates one step
|
|
1543
|
+
* before them.
|
|
1544
|
+
*
|
|
1545
|
+
* Chain entries are **qualified names** (`@package/name:ClassName`)
|
|
1546
|
+
* whenever the corresponding registration has one — i.e. for every
|
|
1547
|
+
* class loaded via a manifest or `@smrt()` decorator with a package
|
|
1548
|
+
* context. Classes registered without a package (some tests) fall
|
|
1549
|
+
* back to their simple name.
|
|
1550
|
+
*
|
|
1551
|
+
* Results are cached globally for performance (~100x faster than re-walking).
|
|
1552
|
+
*
|
|
1553
|
+
* @param className - Name of the registered class (simple or qualified)
|
|
1554
|
+
* @returns Array of class names from oldest registered ancestor to
|
|
1555
|
+
* the input class, or empty array if not found
|
|
1556
|
+
* @example
|
|
1557
|
+
* ```typescript
|
|
1558
|
+
* const chain = ObjectRegistry.getInheritanceChain(
|
|
1559
|
+
* '@happyvertical/smrt-content:BentleyContent',
|
|
1560
|
+
* );
|
|
1561
|
+
* // [
|
|
1562
|
+
* // '@happyvertical/smrt-content:Content',
|
|
1563
|
+
* // '@happyvertical/smrt-content:PraecoContent',
|
|
1564
|
+
* // '@happyvertical/smrt-content:BentleyContent',
|
|
1565
|
+
* // ]
|
|
1566
|
+
* ```
|
|
1567
|
+
*/
|
|
1568
|
+
static getInheritanceChain(className) {
|
|
1569
|
+
return getInheritanceChain(className);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Get all fields including inherited ones from parent classes
|
|
1573
|
+
*
|
|
1574
|
+
* **Hybrid approach (v0.17+):**
|
|
1575
|
+
* - External packages: Use build-time merged fields from manifests
|
|
1576
|
+
* - Local classes: Use runtime merging by walking inheritance chain
|
|
1577
|
+
*
|
|
1578
|
+
* @param className - Name of the registered class
|
|
1579
|
+
* @returns Map of all fields (including inherited)
|
|
1580
|
+
*/
|
|
1581
|
+
static async getAllFields(className) {
|
|
1582
|
+
return getAllFields(className);
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Merge field configurations from parent and child.
|
|
1586
|
+
*
|
|
1587
|
+
* Thin wrapper around the single shared implementation in
|
|
1588
|
+
* registry/inheritance-resolver (#1378). The previous inline copy here had
|
|
1589
|
+
* drifted from the module; both paths now run the same code. Kept as a
|
|
1590
|
+
* private static so existing reach-in tests (issue #841 field-merge coverage)
|
|
1591
|
+
* still resolve it.
|
|
1592
|
+
*
|
|
1593
|
+
* @param parentField - Field config from parent class
|
|
1594
|
+
* @param childField - Field config from child class
|
|
1595
|
+
* @param fieldName - Name of the field (for warning messages)
|
|
1596
|
+
* @returns Merged field configuration
|
|
1597
|
+
*/
|
|
1598
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: reached in by issue #841 field-merge tests via `(ObjectRegistry as any).mergeFieldConfigs`; kept as the registry-side surface delegating to the shared resolver impl.
|
|
1599
|
+
static mergeFieldConfigs(parentField, childField, fieldName) {
|
|
1600
|
+
return mergeFieldConfigs(parentField, childField, fieldName);
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Get all methods including inherited ones from parent classes
|
|
1604
|
+
*
|
|
1605
|
+
* Walks the full inheritance chain and merges methods:
|
|
1606
|
+
* - Parent methods are added first
|
|
1607
|
+
* - Child methods override parent methods (no config merging for methods)
|
|
1608
|
+
*
|
|
1609
|
+
* Results are cached per-class for performance.
|
|
1610
|
+
*
|
|
1611
|
+
* **Note:** This is an async method that ensures manifests are loaded for external package classes.
|
|
1612
|
+
*
|
|
1613
|
+
* @param className - Name of the registered class
|
|
1614
|
+
* @returns Promise resolving to Map of all methods (own + inherited)
|
|
1615
|
+
* @example
|
|
1616
|
+
* ```typescript
|
|
1617
|
+
* // Given: Content → PraecoContent → BentleyContent
|
|
1618
|
+
* const allMethods = await ObjectRegistry.getAllMethods('BentleyContent');
|
|
1619
|
+
* // Includes: generateSummary() (from PraecoContent) + analyzeLocal() (from BentleyContent)
|
|
1620
|
+
* ```
|
|
1621
|
+
*/
|
|
1622
|
+
static async getAllMethods(className) {
|
|
1623
|
+
return getAllMethods(className);
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Get complete metadata for a single object (convenience method)
|
|
1627
|
+
*
|
|
1628
|
+
* Returns all available metadata for an object in a single call, including:
|
|
1629
|
+
* - Class information
|
|
1630
|
+
* - Field definitions
|
|
1631
|
+
* - Configuration
|
|
1632
|
+
* - Schema definition
|
|
1633
|
+
* - Validators
|
|
1634
|
+
* - Relationships
|
|
1635
|
+
* - Tools (AI-callable methods)
|
|
1636
|
+
*
|
|
1637
|
+
* This is a convenience method that aggregates multiple registry queries
|
|
1638
|
+
* into a single comprehensive metadata object.
|
|
1639
|
+
*
|
|
1640
|
+
* @param className - Name of the class to get metadata for
|
|
1641
|
+
* @returns Complete metadata object or null if class not found
|
|
1642
|
+
* @example
|
|
1643
|
+
* ```typescript
|
|
1644
|
+
* const productMeta = ObjectRegistry.getObjectMetadata('Product');
|
|
1645
|
+
* if (productMeta) {
|
|
1646
|
+
* console.log('Name:', productMeta.name);
|
|
1647
|
+
* console.log('Table:', productMeta.schema.tableName);
|
|
1648
|
+
* console.log('Fields:', productMeta.fields.size);
|
|
1649
|
+
* console.log('API config:', productMeta.config.api);
|
|
1650
|
+
* console.log('Relationships:', productMeta.relationships.length);
|
|
1651
|
+
* }
|
|
1652
|
+
* ```
|
|
1653
|
+
*/
|
|
1654
|
+
static getObjectMetadata(className) {
|
|
1655
|
+
const registered = ObjectRegistry.findClass(className);
|
|
1656
|
+
if (!registered) {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
return {
|
|
1660
|
+
name: registered.name,
|
|
1661
|
+
constructor: registered.constructor,
|
|
1662
|
+
collectionConstructor: registered.collectionConstructor,
|
|
1663
|
+
config: registered.config,
|
|
1664
|
+
fields: new Map(registered.fields),
|
|
1665
|
+
// Return copy to prevent mutations
|
|
1666
|
+
methods: new Map(registered.methods),
|
|
1667
|
+
// Return copy to prevent mutations
|
|
1668
|
+
schema: registered.schema,
|
|
1669
|
+
validators: registered.validators || [],
|
|
1670
|
+
relationships: ObjectRegistry.getRelationships(className),
|
|
1671
|
+
inverseRelationships: ObjectRegistry.getInverseRelationships(className),
|
|
1672
|
+
tools: registered.tools
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Get metadata for all registered objects (convenience method)
|
|
1677
|
+
*
|
|
1678
|
+
* Returns comprehensive metadata for every registered object, combining
|
|
1679
|
+
* multiple registry queries into a single convenient data structure.
|
|
1680
|
+
*
|
|
1681
|
+
* This is particularly useful for:
|
|
1682
|
+
* - Admin dashboards showing all objects
|
|
1683
|
+
* - Documentation generation
|
|
1684
|
+
* - Schema visualization
|
|
1685
|
+
* - Debugging and introspection
|
|
1686
|
+
*
|
|
1687
|
+
* @returns Array of complete metadata objects for all registered classes
|
|
1688
|
+
* @example
|
|
1689
|
+
* ```typescript
|
|
1690
|
+
* const allMetadata = ObjectRegistry.getAllObjectMetadata();
|
|
1691
|
+
*
|
|
1692
|
+
* // Generate admin dashboard
|
|
1693
|
+
* for (const meta of allMetadata) {
|
|
1694
|
+
* console.log(`${meta.name}:`);
|
|
1695
|
+
* console.log(` Table: ${meta.schema?.tableName}`);
|
|
1696
|
+
* console.log(` Fields: ${meta.fields.size}`);
|
|
1697
|
+
* console.log(` API: ${meta.config.api ? 'enabled' : 'disabled'}`);
|
|
1698
|
+
* console.log(` Relationships: ${meta.relationships.length}`);
|
|
1699
|
+
* }
|
|
1700
|
+
*
|
|
1701
|
+
* // Generate schema documentation
|
|
1702
|
+
* const schemaDoc = allMetadata.map(meta => ({
|
|
1703
|
+
* name: meta.name,
|
|
1704
|
+
* table: meta.schema?.tableName,
|
|
1705
|
+
* fields: Array.from(meta.fields.entries()).map(([name, field]) => ({
|
|
1706
|
+
* name,
|
|
1707
|
+
* type: field.type,
|
|
1708
|
+
* required: field._meta?.required || false
|
|
1709
|
+
* })),
|
|
1710
|
+
* relationships: meta.relationships.map(rel => ({
|
|
1711
|
+
* field: rel.fieldName,
|
|
1712
|
+
* target: rel.targetClass,
|
|
1713
|
+
* type: rel.type
|
|
1714
|
+
* }))
|
|
1715
|
+
* }));
|
|
1716
|
+
* ```
|
|
1717
|
+
*/
|
|
1718
|
+
static getAllObjectMetadata() {
|
|
1719
|
+
const allMetadata = [];
|
|
1720
|
+
for (const [_key, entry] of ObjectRegistry.classes) {
|
|
1721
|
+
const metadata = ObjectRegistry.getObjectMetadata(entry.name || _key);
|
|
1722
|
+
if (metadata) {
|
|
1723
|
+
allMetadata.push(metadata);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return allMetadata;
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Get inverse relationships (relationships where this class is the target)
|
|
1730
|
+
*
|
|
1731
|
+
* @param className - Name of the class to find inverse relationships for
|
|
1732
|
+
* @returns Array of relationship metadata where this class is the target
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```typescript
|
|
1735
|
+
* const customerInverseRels = ObjectRegistry.getInverseRelationships('Customer');
|
|
1736
|
+
* // [{ sourceClass: 'Order', fieldName: 'customerId', targetClass: 'Customer', ... }]
|
|
1737
|
+
* ```
|
|
1738
|
+
*/
|
|
1739
|
+
static getInverseRelationships(className) {
|
|
1740
|
+
const allRelationships = ObjectRegistry.getRelationshipMap();
|
|
1741
|
+
const inverseRelationships = [];
|
|
1742
|
+
for (const [_sourceClass, relationships] of allRelationships) {
|
|
1743
|
+
for (const rel of relationships) {
|
|
1744
|
+
if (rel.targetClass === className) {
|
|
1745
|
+
inverseRelationships.push(rel);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return inverseRelationships;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Names by which an instance of `className` can be referenced by an inverse
|
|
1753
|
+
* foreign key: its own (simple) class name plus every registered ancestor in
|
|
1754
|
+
* its inheritance chain.
|
|
1755
|
+
*
|
|
1756
|
+
* Used by oneToMany resolution so an STI subclass can resolve a relationship
|
|
1757
|
+
* declared on its base — the inverse `@foreignKey` is declared against the
|
|
1758
|
+
* base class name, while the runtime instance may be a subclass.
|
|
1759
|
+
*
|
|
1760
|
+
* @param className - Simple or qualified class name
|
|
1761
|
+
* @returns Set of names (simple and, where available, qualified) the
|
|
1762
|
+
* instance is assignable to
|
|
1763
|
+
*/
|
|
1764
|
+
static getSelfReferableNames(className) {
|
|
1765
|
+
const names = /* @__PURE__ */ new Set([className]);
|
|
1766
|
+
for (const ancestor of ObjectRegistry.getInheritanceChain(className)) {
|
|
1767
|
+
names.add(ancestor);
|
|
1768
|
+
const simple = ObjectRegistry.getClass(ancestor)?.name;
|
|
1769
|
+
if (simple) {
|
|
1770
|
+
names.add(simple);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return names;
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Inverse relationships targeting `className` OR any (STI) ancestor it
|
|
1777
|
+
* inherits from.
|
|
1778
|
+
*
|
|
1779
|
+
* `getInverseRelationships` matches the target class name exactly. This
|
|
1780
|
+
* variant also matches inverse foreign keys declared against an ancestor,
|
|
1781
|
+
* so an STI subclass instance can resolve a `@oneToMany` declared on its
|
|
1782
|
+
* base — whose inverse `@foreignKey` points at the base class name.
|
|
1783
|
+
*
|
|
1784
|
+
* @param className - Name of the class to find inverse relationships for
|
|
1785
|
+
* @returns Relationship metadata whose target is the class or one of its
|
|
1786
|
+
* registered ancestors
|
|
1787
|
+
*/
|
|
1788
|
+
static getInverseRelationshipsForSelf(className) {
|
|
1789
|
+
const names = ObjectRegistry.getSelfReferableNames(className);
|
|
1790
|
+
const result = [];
|
|
1791
|
+
for (const [, relationships] of ObjectRegistry.getRelationshipMap()) {
|
|
1792
|
+
for (const rel of relationships) {
|
|
1793
|
+
if (names.has(rel.targetClass)) {
|
|
1794
|
+
result.push(rel);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return result;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Get table inheritance strategy for a class
|
|
1802
|
+
*
|
|
1803
|
+
* Returns the table strategy (CTI or STI) for a class, with automatic
|
|
1804
|
+
* inheritance from parent classes. If not explicitly configured,
|
|
1805
|
+
* walks up the inheritance chain to find the strategy.
|
|
1806
|
+
*
|
|
1807
|
+
* **Strategy Inheritance:**
|
|
1808
|
+
* - Set once on base class, children inherit automatically
|
|
1809
|
+
* - Children can explicitly override (not recommended)
|
|
1810
|
+
* - Default is 'cti' if not found in hierarchy
|
|
1811
|
+
*
|
|
1812
|
+
* @param className - Name of the class to get strategy for
|
|
1813
|
+
* @returns 'cti' (Class Table Inheritance) or 'sti' (Single Table Inheritance)
|
|
1814
|
+
* @example
|
|
1815
|
+
* ```typescript
|
|
1816
|
+
* @smrt({ tableStrategy: 'sti' })
|
|
1817
|
+
* class Event extends SmrtObject { }
|
|
1818
|
+
*
|
|
1819
|
+
* @smrt() // Inherits 'sti'
|
|
1820
|
+
* class Meeting extends Event { }
|
|
1821
|
+
*
|
|
1822
|
+
* ObjectRegistry.getTableStrategy('Meeting'); // 'sti'
|
|
1823
|
+
* ObjectRegistry.getTableStrategy('Event'); // 'sti'
|
|
1824
|
+
* ```
|
|
1825
|
+
*/
|
|
1826
|
+
static getTableStrategy(className) {
|
|
1827
|
+
const registered = ObjectRegistry.findClass(className);
|
|
1828
|
+
if (!registered) {
|
|
1829
|
+
return "cti";
|
|
1830
|
+
}
|
|
1831
|
+
if (registered.config?.tableStrategy) {
|
|
1832
|
+
return registered.config.tableStrategy;
|
|
1833
|
+
}
|
|
1834
|
+
const chain = ObjectRegistry.getInheritanceChain(className);
|
|
1835
|
+
for (const ancestorName of chain) {
|
|
1836
|
+
const ancestor = ObjectRegistry.findClass(ancestorName);
|
|
1837
|
+
if (ancestor?.config?.tableStrategy) {
|
|
1838
|
+
return ancestor.config.tableStrategy;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return "cti";
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Get the conflict columns for UPSERT operations on a class
|
|
1845
|
+
*
|
|
1846
|
+
* Returns custom conflict columns if specified, otherwise defaults based on
|
|
1847
|
+
* table strategy:
|
|
1848
|
+
* - CTI: ['slug', 'context']
|
|
1849
|
+
* - STI: ['slug', 'context', '_meta_type']
|
|
1850
|
+
*
|
|
1851
|
+
* STI subclasses share a table, so the discriminator participates in
|
|
1852
|
+
* identity — two subtypes can coexist with the same (slug, context). The
|
|
1853
|
+
* matching unique constraint on `(slug, context, _meta_type)` must exist
|
|
1854
|
+
* in the live schema; if a deployment carries a stale 2-column unique
|
|
1855
|
+
* instead, run `smrt db:migrate` to repair it (see issue #1165).
|
|
1856
|
+
*
|
|
1857
|
+
* @param className - Name of the class to get conflict columns for
|
|
1858
|
+
* @returns Array of column names to use for conflict detection
|
|
1859
|
+
*
|
|
1860
|
+
* @example
|
|
1861
|
+
* ```typescript
|
|
1862
|
+
* // Junction table with custom conflict columns
|
|
1863
|
+
* @smrt({ conflictColumns: ['event_id', 'profile_id'] })
|
|
1864
|
+
* class EventParticipant extends SmrtObject {}
|
|
1865
|
+
*
|
|
1866
|
+
* ObjectRegistry.getConflictColumns('EventParticipant');
|
|
1867
|
+
* // Returns: ['event_id', 'profile_id']
|
|
1868
|
+
* ```
|
|
1869
|
+
*/
|
|
1870
|
+
static getConflictColumns(className) {
|
|
1871
|
+
const registered = ObjectRegistry.findClass(className);
|
|
1872
|
+
if (!registered) {
|
|
1873
|
+
return ["slug", "context"];
|
|
1874
|
+
}
|
|
1875
|
+
if (registered.config?.conflictColumns) {
|
|
1876
|
+
return registered.config.conflictColumns;
|
|
1877
|
+
}
|
|
1878
|
+
const tableStrategy = ObjectRegistry.getTableStrategy(className);
|
|
1879
|
+
return tableStrategy === "sti" ? ["slug", "context", "_meta_type"] : ["slug", "context"];
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Get the tenant scoping configuration for a class (Issue #688)
|
|
1883
|
+
*
|
|
1884
|
+
* Returns the normalized tenant scoping configuration if the class
|
|
1885
|
+
* was registered with `tenantScoped: true` or a tenantScoped config object.
|
|
1886
|
+
*
|
|
1887
|
+
* @param className - Name of the class to check
|
|
1888
|
+
* @returns Tenant scoping config or undefined if not tenant-scoped
|
|
1889
|
+
*
|
|
1890
|
+
* @example
|
|
1891
|
+
* ```typescript
|
|
1892
|
+
* @smrt({ tenantScoped: true })
|
|
1893
|
+
* class Document extends SmrtObject { }
|
|
1894
|
+
*
|
|
1895
|
+
* const config = ObjectRegistry.getTenantScopedConfig('Document');
|
|
1896
|
+
* // { mode: 'required', field: 'tenantId', autoFilter: true, autoPopulate: true, allowSuperAdminBypass: false }
|
|
1897
|
+
* ```
|
|
1898
|
+
*/
|
|
1899
|
+
static getTenantScopedConfig(className) {
|
|
1900
|
+
const registered = ObjectRegistry.findClass(className);
|
|
1901
|
+
return registered?.tenantScopedConfig;
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Check if a class is tenant-scoped (Issue #688)
|
|
1905
|
+
*
|
|
1906
|
+
* @param className - Name of the class to check
|
|
1907
|
+
* @returns true if the class has tenantScoped configuration
|
|
1908
|
+
*/
|
|
1909
|
+
static isTenantScoped(className) {
|
|
1910
|
+
return ObjectRegistry.getTenantScopedConfig(className) !== void 0;
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Get the base class for an STI hierarchy
|
|
1914
|
+
*
|
|
1915
|
+
* Walks up the inheritance chain to find the first class configured
|
|
1916
|
+
* with `tableStrategy: 'sti'`. This is the class that owns the shared table.
|
|
1917
|
+
*
|
|
1918
|
+
* **Returns:**
|
|
1919
|
+
* - The base class's **qualified name** (`@package/name:ClassName`) when
|
|
1920
|
+
* the registration has one — same convention as
|
|
1921
|
+
* `getInheritanceChain`. Falls back to the simple name only for
|
|
1922
|
+
* registrations without a package context (some test classes).
|
|
1923
|
+
* - `null` if the class uses CTI strategy.
|
|
1924
|
+
*
|
|
1925
|
+
* Accepts either a simple or a qualified name on input.
|
|
1926
|
+
*
|
|
1927
|
+
* @param className - Name of the class to find STI base for
|
|
1928
|
+
* @returns Qualified base class name or null if CTI
|
|
1929
|
+
* @example
|
|
1930
|
+
* ```typescript
|
|
1931
|
+
* @smrt({ tableStrategy: 'sti' })
|
|
1932
|
+
* class Event extends SmrtObject { }
|
|
1933
|
+
*
|
|
1934
|
+
* @smrt()
|
|
1935
|
+
* class Meeting extends Event { }
|
|
1936
|
+
*
|
|
1937
|
+
* ObjectRegistry.getSTIBase('@happyvertical/smrt-events:Meeting');
|
|
1938
|
+
* // '@happyvertical/smrt-events:Event'
|
|
1939
|
+
* ObjectRegistry.getSTIBase('@happyvertical/smrt-events:Event');
|
|
1940
|
+
* // '@happyvertical/smrt-events:Event'
|
|
1941
|
+
* ```
|
|
1942
|
+
*/
|
|
1943
|
+
static getSTIBase(className) {
|
|
1944
|
+
const strategy = ObjectRegistry.getTableStrategy(className);
|
|
1945
|
+
if (strategy === "cti") {
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
const chain = ObjectRegistry.getInheritanceChain(className);
|
|
1949
|
+
for (const ancestorName of chain) {
|
|
1950
|
+
const ancestor = ObjectRegistry.findClass(ancestorName);
|
|
1951
|
+
if (!ancestor) continue;
|
|
1952
|
+
if (ancestor.config?.tableStrategy === "sti") {
|
|
1953
|
+
return ancestor.qualifiedName ?? ancestor.name;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
const self = ObjectRegistry.findClass(className);
|
|
1957
|
+
return self?.qualifiedName ?? self?.name ?? className;
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Get all descendant classes of a base class
|
|
1961
|
+
*
|
|
1962
|
+
* Returns all registered classes that inherit from the specified base class.
|
|
1963
|
+
* Uses the `extends` field from manifest to build the descendant tree.
|
|
1964
|
+
*
|
|
1965
|
+
* **Use cases:**
|
|
1966
|
+
* - Schema generation: Aggregate fields from all children for STI table
|
|
1967
|
+
* - Polymorphic queries: Find all types to instantiate
|
|
1968
|
+
* - Documentation: Show class hierarchy
|
|
1969
|
+
*
|
|
1970
|
+
* Accepts either a simple or a qualified name on input. The returned
|
|
1971
|
+
* descendant names are **qualified** (`@package/name:ClassName`) when
|
|
1972
|
+
* the registration has one — same convention as `getInheritanceChain`
|
|
1973
|
+
* and `getSTIBase`.
|
|
1974
|
+
*
|
|
1975
|
+
* @param className - Name of the base class (simple or qualified)
|
|
1976
|
+
* @returns Array of qualified descendant class names (direct and indirect)
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* @smrt({ tableStrategy: 'sti' })
|
|
1980
|
+
* class Event extends SmrtObject { }
|
|
1981
|
+
*
|
|
1982
|
+
* @smrt()
|
|
1983
|
+
* class Meeting extends Event { }
|
|
1984
|
+
*
|
|
1985
|
+
* @smrt()
|
|
1986
|
+
* class HockeyGame extends Event { }
|
|
1987
|
+
*
|
|
1988
|
+
* ObjectRegistry.getDescendants('@happyvertical/smrt-events:Event');
|
|
1989
|
+
* // [
|
|
1990
|
+
* // '@happyvertical/smrt-events:Meeting',
|
|
1991
|
+
* // '@happyvertical/smrt-events:HockeyGame',
|
|
1992
|
+
* // ]
|
|
1993
|
+
* ```
|
|
1994
|
+
*/
|
|
1995
|
+
static getDescendants(className) {
|
|
1996
|
+
const descendants = [];
|
|
1997
|
+
const seenRegistrations = /* @__PURE__ */ new Set();
|
|
1998
|
+
const target = ObjectRegistry.findClass(className);
|
|
1999
|
+
const targetKey = target?.qualifiedName ?? target?.name ?? className;
|
|
2000
|
+
for (const [_key, childClass] of ObjectRegistry.classes) {
|
|
2001
|
+
if (seenRegistrations.has(childClass)) {
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
seenRegistrations.add(childClass);
|
|
2005
|
+
const childKey = childClass.qualifiedName ?? childClass.name ?? _key;
|
|
2006
|
+
if (childKey === targetKey) continue;
|
|
2007
|
+
const chain = ObjectRegistry.getInheritanceChain(childKey);
|
|
2008
|
+
if (chain.includes(targetKey)) {
|
|
2009
|
+
descendants.push(childKey);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return descendants;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Persist registry state to system tables
|
|
2016
|
+
*
|
|
2017
|
+
* Saves all registered class metadata to the _smrt_registry system table
|
|
2018
|
+
* for runtime introspection and debugging. This enables applications to
|
|
2019
|
+
* query what SMRT objects exist and their configurations.
|
|
2020
|
+
*
|
|
2021
|
+
* @param db - Database interface to persist to
|
|
2022
|
+
* @returns Promise that resolves when persistence is complete
|
|
2023
|
+
* @example
|
|
2024
|
+
* ```typescript
|
|
2025
|
+
* // After registering all classes
|
|
2026
|
+
* await ObjectRegistry.persistToDatabase(db);
|
|
2027
|
+
*
|
|
2028
|
+
* // Later, query the system table
|
|
2029
|
+
* const rows = await db.all('SELECT * FROM _smrt_registry');
|
|
2030
|
+
* console.log('Registered classes:', rows.map(r => r.class_name));
|
|
2031
|
+
* ```
|
|
2032
|
+
*/
|
|
2033
|
+
static async persistToDatabase(db) {
|
|
2034
|
+
for (const [className, registered] of ObjectRegistry.classes.entries()) {
|
|
2035
|
+
const fieldsData = {};
|
|
2036
|
+
for (const [key, value] of registered.fields) {
|
|
2037
|
+
fieldsData[key] = {
|
|
2038
|
+
type: value.type,
|
|
2039
|
+
options: value.options
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
await db.upsert(
|
|
2043
|
+
"_smrt_registry",
|
|
2044
|
+
["class_name"],
|
|
2045
|
+
// PRIMARY KEY for conflict detection
|
|
2046
|
+
{
|
|
2047
|
+
class_name: className,
|
|
2048
|
+
schema_version: "1.0.0",
|
|
2049
|
+
// Could be derived from package version
|
|
2050
|
+
fields: JSON.stringify(fieldsData),
|
|
2051
|
+
relationships: JSON.stringify(
|
|
2052
|
+
ObjectRegistry.getRelationships(className)
|
|
2053
|
+
),
|
|
2054
|
+
config: JSON.stringify(registered.config),
|
|
2055
|
+
manifest: JSON.stringify({
|
|
2056
|
+
name: registered.name,
|
|
2057
|
+
tableName: registered.schema?.tableName,
|
|
2058
|
+
tools: registered.tools
|
|
2059
|
+
}),
|
|
2060
|
+
last_updated: /* @__PURE__ */ new Date()
|
|
2061
|
+
}
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Load registry metadata from system tables
|
|
2067
|
+
*
|
|
2068
|
+
* Reads the _smrt_registry system table to inspect what classes
|
|
2069
|
+
* have been registered. This is primarily for introspection and
|
|
2070
|
+
* debugging - actual class registration happens via @smrt() decorator.
|
|
2071
|
+
*
|
|
2072
|
+
* @param db - Database interface to load from
|
|
2073
|
+
* @returns Promise resolving to array of class metadata
|
|
2074
|
+
* @example
|
|
2075
|
+
* ```typescript
|
|
2076
|
+
* const metadata = await ObjectRegistry.loadFromDatabase(db);
|
|
2077
|
+
* for (const meta of metadata) {
|
|
2078
|
+
* console.log(`Class: ${meta.class_name}`);
|
|
2079
|
+
* console.log(`Table: ${JSON.parse(meta.manifest).tableName}`);
|
|
2080
|
+
* }
|
|
2081
|
+
* ```
|
|
2082
|
+
*/
|
|
2083
|
+
static async loadFromDatabase(db) {
|
|
2084
|
+
const { rows } = await db.query(
|
|
2085
|
+
"SELECT * FROM _smrt_registry ORDER BY class_name"
|
|
2086
|
+
);
|
|
2087
|
+
return rows;
|
|
2088
|
+
}
|
|
2089
|
+
// ============================================================================
|
|
2090
|
+
// Embedding Configuration Methods
|
|
2091
|
+
// ============================================================================
|
|
2092
|
+
/**
|
|
2093
|
+
* Get embedding configuration for a class
|
|
2094
|
+
*
|
|
2095
|
+
* Returns the class-specific embedding config if embeddings are enabled.
|
|
2096
|
+
* This includes the fields to embed, provider override, and generation options.
|
|
2097
|
+
*
|
|
2098
|
+
* @param className - Name of the class to get embedding config for
|
|
2099
|
+
* @returns Class embedding config or undefined if not configured
|
|
2100
|
+
* @example
|
|
2101
|
+
* ```typescript
|
|
2102
|
+
* @smrt({
|
|
2103
|
+
* embeddings: {
|
|
2104
|
+
* fields: ['title', 'body'],
|
|
2105
|
+
* autoGenerate: true
|
|
2106
|
+
* }
|
|
2107
|
+
* })
|
|
2108
|
+
* class Article extends SmrtObject { }
|
|
2109
|
+
*
|
|
2110
|
+
* const config = ObjectRegistry.getEmbeddingConfig('Article');
|
|
2111
|
+
* // { fields: ['title', 'body'], autoGenerate: true }
|
|
2112
|
+
* ```
|
|
2113
|
+
*/
|
|
2114
|
+
static getEmbeddingConfig(className) {
|
|
2115
|
+
return getEmbeddingConfig(className);
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Check if a class has embeddings enabled
|
|
2119
|
+
*
|
|
2120
|
+
* @param className - Name of the class to check
|
|
2121
|
+
* @returns True if the class has embedding configuration
|
|
2122
|
+
* @example
|
|
2123
|
+
* ```typescript
|
|
2124
|
+
* if (ObjectRegistry.hasEmbeddings('Article')) {
|
|
2125
|
+
* await article.generateEmbeddings();
|
|
2126
|
+
* }
|
|
2127
|
+
* ```
|
|
2128
|
+
*/
|
|
2129
|
+
static hasEmbeddings(className) {
|
|
2130
|
+
return hasEmbeddings(className);
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Get all registered classes that have embeddings enabled
|
|
2134
|
+
*
|
|
2135
|
+
* @returns Array of class names with embedding configuration
|
|
2136
|
+
* @example
|
|
2137
|
+
* ```typescript
|
|
2138
|
+
* const embeddableClasses = ObjectRegistry.getEmbeddingClasses();
|
|
2139
|
+
* // ['Article', 'Profile', 'Event']
|
|
2140
|
+
* ```
|
|
2141
|
+
*/
|
|
2142
|
+
static getEmbeddingClasses() {
|
|
2143
|
+
return getEmbeddingClasses();
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Get project-level embedding configuration
|
|
2147
|
+
*
|
|
2148
|
+
* Returns the global embedding settings from smrt.config (or defaults).
|
|
2149
|
+
* These settings apply to all classes unless overridden at the class level.
|
|
2150
|
+
*
|
|
2151
|
+
* @returns Project embedding configuration with defaults applied
|
|
2152
|
+
* @example
|
|
2153
|
+
* ```typescript
|
|
2154
|
+
* const projectConfig = ObjectRegistry.getProjectEmbeddingConfig();
|
|
2155
|
+
* // {
|
|
2156
|
+
* // dimensions: 768,
|
|
2157
|
+
* // provider: 'local',
|
|
2158
|
+
* // localModel: 'Xenova/bge-base-en-v1.5',
|
|
2159
|
+
* // aiModel: 'text-embedding-3-small',
|
|
2160
|
+
* // fallbackToAI: false
|
|
2161
|
+
* // }
|
|
2162
|
+
* ```
|
|
2163
|
+
*/
|
|
2164
|
+
static getProjectEmbeddingConfig() {
|
|
2165
|
+
return getProjectEmbeddingConfig();
|
|
2166
|
+
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Resolve complete embedding configuration for a class
|
|
2169
|
+
*
|
|
2170
|
+
* Merges project-level config with class-level overrides.
|
|
2171
|
+
* Returns undefined if the class doesn't have embeddings enabled.
|
|
2172
|
+
*
|
|
2173
|
+
* @param className - Name of the class to resolve config for
|
|
2174
|
+
* @returns Fully resolved embedding config or undefined
|
|
2175
|
+
* @example
|
|
2176
|
+
* ```typescript
|
|
2177
|
+
* const config = ObjectRegistry.resolveEmbeddingConfig('Article');
|
|
2178
|
+
* // Merges project defaults with class-specific settings
|
|
2179
|
+
* // {
|
|
2180
|
+
* // fields: ['title', 'body'],
|
|
2181
|
+
* // dimensions: 768,
|
|
2182
|
+
* // provider: 'local',
|
|
2183
|
+
* // localModel: 'Xenova/bge-base-en-v1.5',
|
|
2184
|
+
* // autoGenerate: true,
|
|
2185
|
+
* // regenerateOnChange: true,
|
|
2186
|
+
* // ...
|
|
2187
|
+
* // }
|
|
2188
|
+
* ```
|
|
2189
|
+
*/
|
|
2190
|
+
static resolveEmbeddingConfig(className) {
|
|
2191
|
+
return resolveEmbeddingConfig(className);
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Resolve the effective collection read-cache config for a class (issue #1498)
|
|
2195
|
+
*
|
|
2196
|
+
* Walks the inheritance chain nearest-first so an STI base class opt-in
|
|
2197
|
+
* covers its children, while a child can opt back out with `cache: false`.
|
|
2198
|
+
*
|
|
2199
|
+
* @param className - Simple or qualified class name
|
|
2200
|
+
* @returns Effective cache config, or undefined when caching is not opted in
|
|
2201
|
+
*/
|
|
2202
|
+
static resolveCollectionCacheConfig(className) {
|
|
2203
|
+
return resolveCollectionCacheConfig(className);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function smrt(config = {}) {
|
|
2207
|
+
return (ctor, decoratorContext) => {
|
|
2208
|
+
applyPendingDecoratorRegistrations(ctor, decoratorContext);
|
|
2209
|
+
const isCollection = ctor.prototype instanceof SmrtCollection;
|
|
2210
|
+
if (isCollection) {
|
|
2211
|
+
const itemClass = ctor._itemClass;
|
|
2212
|
+
if (itemClass) {
|
|
2213
|
+
let tableName = config.tableName;
|
|
2214
|
+
if (!tableName) {
|
|
2215
|
+
const itemClassName = itemClass.name;
|
|
2216
|
+
const itemReg = ObjectRegistry.getClassByConstructor(
|
|
2217
|
+
itemClass
|
|
2218
|
+
);
|
|
2219
|
+
const itemQualified = itemReg?.qualifiedName ?? itemClassName;
|
|
2220
|
+
const stiBase = ObjectRegistry.getSTIBase(itemQualified);
|
|
2221
|
+
if (stiBase && stiBase !== itemQualified) {
|
|
2222
|
+
const baseReg = ObjectRegistry.getClass(stiBase);
|
|
2223
|
+
tableName = baseReg?.schema?.tableName ?? classnameToTablename(baseReg?.name ?? stiBase);
|
|
2224
|
+
} else if (ObjectRegistry.getTableStrategy(itemQualified) === "sti") {
|
|
2225
|
+
tableName = classnameToTablename(itemClassName);
|
|
2226
|
+
} else {
|
|
2227
|
+
tableName = classnameToTablename(itemClassName);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
ObjectRegistry.register(itemClass, { ...config, tableName });
|
|
2231
|
+
const registeredItemClass = ObjectRegistry.getClass(itemClass.name);
|
|
2232
|
+
ObjectRegistry.registerCollection(itemClass.name, ctor);
|
|
2233
|
+
if (registeredItemClass?.qualifiedName) {
|
|
2234
|
+
ObjectRegistry.registerCollection(
|
|
2235
|
+
registeredItemClass.qualifiedName,
|
|
2236
|
+
ctor
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
ObjectRegistry.registerCollection(tableName, ctor);
|
|
2240
|
+
ObjectRegistry.setCollectionTableName(ctor.name, tableName);
|
|
2241
|
+
}
|
|
2242
|
+
} else {
|
|
2243
|
+
let tableName = config.tableName;
|
|
2244
|
+
if (!tableName) {
|
|
2245
|
+
const manifestEntry = discoverCachedManifestSync(ctor.name);
|
|
2246
|
+
if (manifestEntry?.decoratorConfig?.tableName) {
|
|
2247
|
+
tableName = manifestEntry.decoratorConfig.tableName;
|
|
2248
|
+
} else if (config.tableStrategy === "sti") {
|
|
2249
|
+
let proto = Object.getPrototypeOf(ctor);
|
|
2250
|
+
let stiBaseName = null;
|
|
2251
|
+
while (proto?.name && proto.name !== "SmrtObject") {
|
|
2252
|
+
const protoReg = ObjectRegistry.getClassByConstructor(proto);
|
|
2253
|
+
const protoKey = protoReg?.qualifiedName ?? proto.name;
|
|
2254
|
+
if (ObjectRegistry.getTableStrategy(protoKey) === "sti") {
|
|
2255
|
+
stiBaseName = ObjectRegistry.getSTIBase(protoKey);
|
|
2256
|
+
break;
|
|
2257
|
+
}
|
|
2258
|
+
proto = Object.getPrototypeOf(proto);
|
|
2259
|
+
}
|
|
2260
|
+
if (stiBaseName) {
|
|
2261
|
+
const baseReg = ObjectRegistry.getClass(stiBaseName);
|
|
2262
|
+
tableName = baseReg?.schema?.tableName ?? classnameToTablename(baseReg?.name ?? stiBaseName);
|
|
2263
|
+
} else {
|
|
2264
|
+
tableName = classnameToTablename(ctor.name);
|
|
2265
|
+
}
|
|
2266
|
+
} else {
|
|
2267
|
+
let proto = Object.getPrototypeOf(ctor);
|
|
2268
|
+
let stiBaseName = null;
|
|
2269
|
+
while (proto?.name && proto.name !== "SmrtObject") {
|
|
2270
|
+
const protoReg = ObjectRegistry.getClassByConstructor(proto);
|
|
2271
|
+
const protoKey = protoReg?.qualifiedName ?? proto.name;
|
|
2272
|
+
if (ObjectRegistry.getTableStrategy(protoKey) === "sti") {
|
|
2273
|
+
stiBaseName = ObjectRegistry.getSTIBase(protoKey);
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
proto = Object.getPrototypeOf(proto);
|
|
2277
|
+
}
|
|
2278
|
+
if (stiBaseName) {
|
|
2279
|
+
const baseReg = ObjectRegistry.getClass(stiBaseName);
|
|
2280
|
+
tableName = baseReg?.schema?.tableName ?? classnameToTablename(baseReg?.name ?? stiBaseName);
|
|
2281
|
+
} else {
|
|
2282
|
+
tableName = classnameToTablename(ctor.name);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
ObjectRegistry.register(ctor, { ...config, tableName });
|
|
2287
|
+
applyOneToManyChildAccessors(
|
|
2288
|
+
ctor,
|
|
2289
|
+
ObjectRegistry.getRelationships(ctor.name)
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
return ctor;
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
export {
|
|
2296
|
+
ObjectRegistry,
|
|
2297
|
+
SMRT_COLLECTION_BASE_NAMES,
|
|
2298
|
+
isSmrtCollectionExtendsName,
|
|
2299
|
+
smrt
|
|
2300
|
+
};
|
|
2301
|
+
//# sourceMappingURL=registry.js.map
|