@happyvertical/smrt-core 0.51.39 → 0.52.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.
Files changed (79) hide show
  1. package/AGENTS.md +8 -0
  2. package/agents/schema-paths.md +79 -0
  3. package/dist/cascade.d.ts.map +1 -1
  4. package/dist/cascade.js +2 -1
  5. package/dist/cascade.js.map +1 -1
  6. package/dist/collection.d.ts +10 -1
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +14 -4
  9. package/dist/collection.js.map +1 -1
  10. package/dist/errors.d.ts +18 -0
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/errors.js +21 -0
  13. package/dist/errors.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/manifest/static-manifest.d.ts.map +1 -1
  18. package/dist/manifest/static-manifest.js +9 -1
  19. package/dist/manifest/static-manifest.js.map +1 -1
  20. package/dist/manifest/store.js +1 -1
  21. package/dist/manifest/store.js.map +1 -1
  22. package/dist/manifest.json +9 -1
  23. package/dist/migrations/differ.d.ts +56 -0
  24. package/dist/migrations/differ.d.ts.map +1 -1
  25. package/dist/migrations/differ.js +152 -27
  26. package/dist/migrations/differ.js.map +1 -1
  27. package/dist/migrations/null-equal-indexes.js +1 -1
  28. package/dist/migrations/orchestrate.d.ts +7 -0
  29. package/dist/migrations/orchestrate.d.ts.map +1 -1
  30. package/dist/migrations/orchestrate.js +4 -2
  31. package/dist/migrations/orchestrate.js.map +1 -1
  32. package/dist/migrations/tracker.d.ts +29 -1
  33. package/dist/migrations/tracker.d.ts.map +1 -1
  34. package/dist/migrations/tracker.js +92 -7
  35. package/dist/migrations/tracker.js.map +1 -1
  36. package/dist/object.d.ts +128 -0
  37. package/dist/object.d.ts.map +1 -1
  38. package/dist/object.js +333 -44
  39. package/dist/object.js.map +1 -1
  40. package/dist/registry/schema-builder.d.ts.map +1 -1
  41. package/dist/registry/schema-builder.js +7 -5
  42. package/dist/registry/schema-builder.js.map +1 -1
  43. package/dist/registry.d.ts +68 -4
  44. package/dist/registry.d.ts.map +1 -1
  45. package/dist/registry.js +119 -8
  46. package/dist/registry.js.map +1 -1
  47. package/dist/scanner/index.d.ts +1 -1
  48. package/dist/scanner/index.d.ts.map +1 -1
  49. package/dist/scanner/manifest-generator.d.ts +3 -2
  50. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  51. package/dist/scanner/manifest-generator.js +12 -8
  52. package/dist/scanner/manifest-generator.js.map +1 -1
  53. package/dist/scanner/types.d.ts +21 -0
  54. package/dist/scanner/types.d.ts.map +1 -1
  55. package/dist/scanner/types.js.map +1 -1
  56. package/dist/schema/conflict-target.d.ts +68 -11
  57. package/dist/schema/conflict-target.d.ts.map +1 -1
  58. package/dist/schema/conflict-target.js +68 -12
  59. package/dist/schema/conflict-target.js.map +1 -1
  60. package/dist/schema/foreign-key-ddl.d.ts +17 -1
  61. package/dist/schema/foreign-key-ddl.d.ts.map +1 -1
  62. package/dist/schema/foreign-key-ddl.js +29 -3
  63. package/dist/schema/foreign-key-ddl.js.map +1 -1
  64. package/dist/schema/foreign-key-policy.d.ts +8 -0
  65. package/dist/schema/foreign-key-policy.d.ts.map +1 -1
  66. package/dist/schema/foreign-key-policy.js +2 -1
  67. package/dist/schema/foreign-key-policy.js.map +1 -1
  68. package/dist/schema/generator.d.ts +7 -0
  69. package/dist/schema/generator.d.ts.map +1 -1
  70. package/dist/schema/generator.js +18 -10
  71. package/dist/schema/generator.js.map +1 -1
  72. package/dist/schema/text-cast-probe.d.ts +50 -1
  73. package/dist/schema/text-cast-probe.d.ts.map +1 -1
  74. package/dist/schema/text-cast-probe.js +46 -11
  75. package/dist/schema/text-cast-probe.js.map +1 -1
  76. package/dist/schema/types.d.ts +6 -0
  77. package/dist/schema/types.d.ts.map +1 -1
  78. package/dist/smrt-knowledge.json +12 -7
  79. package/package.json +10 -10
package/dist/registry.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { ConfigurationError } from "./errors.js";
2
+ import { defaultConflictColumns, resolveOwnershipTenantColumn, resolveTenantColumn } from "./schema/conflict-target.js";
2
3
  import { classnameToTablename, toSnakeCase } from "./utils/naming.js";
4
+ import { buildCascadePlan } from "./cascade.js";
3
5
  import { applyOneToManyChildAccessors } from "./child-accessors.js";
4
6
  import { applyPendingDecoratorRegistrations } from "./decorators/compatibility.js";
5
7
  import { bumpRegistryGeneration, getRegistryGeneration } from "./registry/generation.js";
6
8
  import { createQualifiedName, isQualifiedName, parseQualifiedName } from "./utils/qualified-names.js";
7
9
  import { LRUCache } from "./utils/lru-cache.js";
8
10
  import { getClasses, getCollectionCache, getCollectionTableNames, getCollections, getConstructorFieldDecorators, getConstructorTenantScopedDeclarations, getDbInstanceIds, getDiscoveryAttemptCache, getFieldDecorators, getInheritanceCache, getLegacyFieldDecorators, getMethodDecorators, getNextDbId, getSourceFileFromStack, getStiSiblingsLoaded, setNextDbId, verboseLog } from "./registry/shared-state.js";
9
- import { defaultConflictColumns, resolveTenantColumn } from "./schema/conflict-target.js";
10
11
  import { clearRegistryDiagnostics, flushRegistryDiagnostics, getRegistryDiagnostics, recordRegistryDiagnostic } from "./registry/diagnostics.js";
11
12
  import { cloneManifestSchemaColumns, discoverCachedManifestSync, getManifestCache, getNodeBuiltins, loadExternalManifestSyncWithNode } from "./manifest/store.js";
12
13
  import { getDefaultCompositeSource } from "./manifest/sources/composite.js";
@@ -99,11 +100,17 @@ function applyManifestFieldColumnMetadata(columns, fieldEntries) {
99
100
  const columnName = toSnakeCase(fieldName);
100
101
  const existingColumn = columns[columnName];
101
102
  if (!existingColumn) continue;
102
- columns[columnName] = {
103
+ const column = {
103
104
  ...existingColumn,
104
105
  ...sqlType ? { type: String(sqlType).toUpperCase() } : {},
105
106
  ...referenceKind ? { referenceKind } : {}
106
107
  };
108
+ if (String(column.type).toUpperCase() === "UUID") {
109
+ const loose = column;
110
+ if (loose.default === "") delete loose.default;
111
+ if (loose.defaultValue === "") delete loose.defaultValue;
112
+ }
113
+ columns[columnName] = column;
107
114
  }
108
115
  }
109
116
  async function discoverInstalledSmrtPackages() {
@@ -143,6 +150,22 @@ function resolveManifestByUpwardSearch(builtins, missingPath) {
143
150
  return null;
144
151
  }
145
152
  /**
153
+ * The absolute filesystem path behind a Vite dev-server URL
154
+ * (`http(s)://host/@fs/<absolute path>`), or `null` for any other string.
155
+ */
156
+ function viteFsUrlToPath(urlString) {
157
+ if (!/^https?:\/\//i.test(urlString)) return null;
158
+ let pathname;
159
+ try {
160
+ pathname = new URL(urlString).pathname;
161
+ } catch {
162
+ return null;
163
+ }
164
+ if (!pathname.startsWith("/@fs/")) return null;
165
+ const decoded = decodeURIComponent(pathname.slice(4));
166
+ return /^\/[A-Za-z]:\//.test(decoded) ? decoded.slice(1) : decoded;
167
+ }
168
+ /**
146
169
  * Central registry for all SMRT objects
147
170
  *
148
171
  * Uses globalThis for cross-module state sharing, ensuring all module instances
@@ -1069,8 +1092,10 @@ var ObjectRegistry = class ObjectRegistry {
1069
1092
  let manifest = null;
1070
1093
  try {
1071
1094
  const urlString = manifestUrl instanceof URL ? manifestUrl.href : manifestUrl;
1095
+ const devServerPath = viteFsUrlToPath(urlString);
1072
1096
  let filePath;
1073
- if (manifestUrl instanceof URL) filePath = builtins.url.fileURLToPath(manifestUrl);
1097
+ if (devServerPath) filePath = devServerPath;
1098
+ else if (manifestUrl instanceof URL) filePath = builtins.url.fileURLToPath(manifestUrl);
1074
1099
  else if (urlString.startsWith("file:")) filePath = builtins.url.fileURLToPath(urlString);
1075
1100
  else filePath = urlString;
1076
1101
  if (!builtins.fs.existsSync(filePath)) {
@@ -2004,10 +2029,11 @@ var ObjectRegistry = class ObjectRegistry {
2004
2029
  * primary-key column(s) — the only unique key such a table has;
2005
2030
  * 4. the strategy default — CTI `['slug', 'context']`, STI
2006
2031
  * `['slug', 'context', '_meta_type']` — led by the tenant column when
2007
- * the schema owner is tenant-scoped (#2360): tenant-scoped rows are
2008
- * unique per tenant, and without the tenant column a second tenant's
2009
- * `save()` of the same natural key updated the first tenant's row
2010
- * through `DO UPDATE SET`.
2032
+ * the schema owner is tenant-scoped (#2360) or carries an undeclared
2033
+ * `tenantId` field ({@link ObjectRegistry.getOwnershipTenantColumn}):
2034
+ * tenant-owned rows are unique per tenant, and without the tenant column
2035
+ * a second tenant's `save()` of the same natural key updated the first
2036
+ * tenant's row through `DO UPDATE SET`.
2011
2037
  *
2012
2038
  * STI subclasses share a table, so the discriminator participates in
2013
2039
  * identity — two subtypes can coexist with the same (slug, context). The
@@ -2053,7 +2079,92 @@ var ObjectRegistry = class ObjectRegistry {
2053
2079
  }
2054
2080
  const primaryKeyColumns = Array.from(owner.fields.entries()).filter(([, field]) => field?._meta?.primaryKey === true).map(([fieldName]) => toSnakeCase(fieldName));
2055
2081
  if (primaryKeyColumns.length > 0) return primaryKeyColumns;
2056
- return defaultConflictColumns(tableStrategy, ObjectRegistry.getTenantColumn(className));
2082
+ return defaultConflictColumns(tableStrategy, ObjectRegistry.getOwnershipTenantColumn(className));
2083
+ }
2084
+ /**
2085
+ * The column that owns a table's rows for natural-key identity: the
2086
+ * declared tenant column ({@link ObjectRegistry.getTenantColumn}), else
2087
+ * `tenant_id` when the schema owner has a `tenantId` field but declares no
2088
+ * tenancy — the shape a consumer registers with the tenancy interceptor at
2089
+ * runtime. The default conflict target and unique index lead with it, so a
2090
+ * second tenant's same-slug row is a second row rather than an upsert onto
2091
+ * the first tenant's. See `resolveOwnershipTenantColumn()`.
2092
+ *
2093
+ * @param className - Name of the class (simple or qualified)
2094
+ * @returns The snake_case ownership column, or `undefined`
2095
+ */
2096
+ static getOwnershipTenantColumn(className) {
2097
+ const declared = ObjectRegistry.getTenantColumn(className);
2098
+ const registered = ObjectRegistry.findClass(className);
2099
+ if (!registered) return declared;
2100
+ const ownerName = ObjectRegistry.getTableStrategy(className) === "sti" ? ObjectRegistry.getSTIBase(className) : null;
2101
+ const owner = ownerName && ownerName !== className ? ObjectRegistry.findClass(ownerName) ?? registered : registered;
2102
+ return resolveOwnershipTenantColumn(declared, (columnName) => [...owner.fields.entries()].some(([fieldName, field]) => field?.type !== "oneToMany" && field?.type !== "manyToMany" && toSnakeCase(fieldName) === columnName));
2103
+ }
2104
+ /**
2105
+ * Every typed reference (`@foreignKey` / `@crossPackageRef`) that points at
2106
+ * `className`, resolved the same way `SmrtObject.delete()` resolves them for
2107
+ * cascades: the referencing class, its table and column, and the resolved
2108
+ * `onDelete` action. Use it to re-point references before replacing one row
2109
+ * with another (a tag merge) instead of letting a delete cascade them away.
2110
+ *
2111
+ * @param className - Name of the referenced class (simple or qualified)
2112
+ */
2113
+ static getIncomingReferences(className) {
2114
+ return buildCascadePlan(ObjectRegistry, className).references.map((reference) => ({
2115
+ className: reference.className,
2116
+ tableName: reference.tableName,
2117
+ column: reference.column,
2118
+ onDelete: reference.action
2119
+ }));
2120
+ }
2121
+ /**
2122
+ * Runtime ownership-column sources (see
2123
+ * {@link ObjectRegistry.registerOwnershipColumnSource}).
2124
+ */
2125
+ static ownershipColumnSources = /* @__PURE__ */ new Set();
2126
+ /**
2127
+ * Register a source of RUNTIME ownership columns: columns that record a
2128
+ * row's owner for a class whose manifest does not say so — the tenancy
2129
+ * package's `registerTenantScopedClass('Team', { field: 'organizationId' })`
2130
+ * is the canonical producer.
2131
+ *
2132
+ * Core reads these only to protect ownership on save
2133
+ * ({@link ObjectRegistry.getOwnershipColumns}); they never change the
2134
+ * conflict target or the schema, which are build-time contracts a runtime
2135
+ * registration cannot alter without breaking `ON CONFLICT` binding. The
2136
+ * source is consulted on every lookup, so registrations that bind later (a
2137
+ * selector registered before its class) or are removed take effect
2138
+ * immediately.
2139
+ *
2140
+ * @param source - maps a qualified class name to its runtime ownership
2141
+ * columns (snake_case), or `undefined` when it has none
2142
+ * @returns a disposer that removes the source
2143
+ */
2144
+ static registerOwnershipColumnSource(source) {
2145
+ ObjectRegistry.ownershipColumnSources.add(source);
2146
+ return () => {
2147
+ ObjectRegistry.ownershipColumnSources.delete(source);
2148
+ };
2149
+ }
2150
+ /**
2151
+ * Every column that records a row's owner for natural-key save protection:
2152
+ * `tenant_id`, the declared tenant column, each `@tenantId`-marked field's
2153
+ * column, and every runtime ownership column a registered source reports
2154
+ * ({@link ObjectRegistry.registerOwnershipColumnSource}) — so a runtime
2155
+ * tenancy registration on a custom field (`organizationId`) is compared like
2156
+ * `tenant_id`.
2157
+ *
2158
+ * @param className - Name of the class (simple or qualified)
2159
+ */
2160
+ static getOwnershipColumns(className) {
2161
+ const columns = /* @__PURE__ */ new Set(["tenant_id"]);
2162
+ const declared = ObjectRegistry.getTenantColumn(className);
2163
+ if (declared) columns.add(declared);
2164
+ for (const [name, field] of ObjectRegistry.getFields(className)) if (field.__tenancy?.isTenantIdField || field._meta?.__tenancy?.isTenantIdField) columns.add(toSnakeCase(name));
2165
+ const qualifiedName = ObjectRegistry.findClass(className)?.qualifiedName ?? className;
2166
+ for (const source of ObjectRegistry.ownershipColumnSources) for (const column of source(qualifiedName) ?? []) if (column) columns.add(column);
2167
+ return columns;
2057
2168
  }
2058
2169
  /**
2059
2170
  * The tenant column of the table a class writes to, or `undefined` when its