@rebasepro/common 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.umd.js CHANGED
@@ -1440,8 +1440,57 @@
1440
1440
  return result;
1441
1441
  }
1442
1442
  //#endregion
1443
+ //#region src/data/resolveDataSource.ts
1444
+ /**
1445
+ * Build a keyed registry from a list of {@link DataSourceDefinition}s.
1446
+ * Later entries win on key collision.
1447
+ */
1448
+ function createDataSourceRegistry(definitions) {
1449
+ const registry = {};
1450
+ for (const def of definitions ?? []) registry[def.key] = def;
1451
+ return registry;
1452
+ }
1453
+ /**
1454
+ * Resolve the effective data source for a collection — the single source of
1455
+ * truth shared by the frontend router, the backend driver registry, and the
1456
+ * editor's capability lookups.
1457
+ *
1458
+ * Resolution order:
1459
+ * 1. The routing **key** is `collection.dataSource`, else the legacy
1460
+ * `collection.driver`, else {@link DEFAULT_DATA_SOURCE_KEY}.
1461
+ * 2. If a definition is registered for that key, it provides `engine`,
1462
+ * `transport`, and `databaseId`.
1463
+ * 3. Otherwise values are synthesized for backward compatibility: `engine`
1464
+ * from the legacy `driver` (or the key, or `"postgres"`), `transport`
1465
+ * defaults to `"server"`, and `databaseId` from the collection.
1466
+ *
1467
+ * `capabilities` are always derived from the resolved `engine`, so two
1468
+ * data sources sharing an engine share capabilities.
1469
+ *
1470
+ * @param collection the collection (or any object carrying the routing fields)
1471
+ * @param registry optional registry of declared data sources
1472
+ */
1473
+ function resolveDataSource(collection, registry) {
1474
+ const key = collection?.dataSource ?? collection?.driver ?? _rebasepro_types.DEFAULT_DATA_SOURCE_KEY;
1475
+ const def = registry?.[key];
1476
+ const engine = def?.engine ?? collection?.driver ?? (key !== _rebasepro_types.DEFAULT_DATA_SOURCE_KEY ? key : "postgres");
1477
+ return {
1478
+ key,
1479
+ engine,
1480
+ transport: def?.transport ?? "server",
1481
+ databaseId: collection?.databaseId ?? def?.databaseId,
1482
+ capabilities: (0, _rebasepro_types.getDataSourceCapabilities)(engine)
1483
+ };
1484
+ }
1485
+ //#endregion
1443
1486
  //#region src/collections/CollectionRegistry.ts
1444
1487
  var CollectionRegistry = class {
1488
+ /**
1489
+ * Declared data sources, used during normalization to resolve each
1490
+ * collection's engine (so `dataSource`-only collections get the right
1491
+ * capabilities). Empty by default → behaviour keys off `driver` as before.
1492
+ */
1493
+ dataSources = {};
1445
1494
  collectionsByTableName = /* @__PURE__ */ new Map();
1446
1495
  collectionsBySlug = /* @__PURE__ */ new Map();
1447
1496
  rootCollections = [];
@@ -1451,9 +1500,20 @@
1451
1500
  rawRootCollections = [];
1452
1501
  cachedRawCollectionsList = null;
1453
1502
  lastRawInputSnapshot = null;
1454
- constructor(collections) {
1503
+ constructor(collections, dataSources) {
1504
+ if (dataSources) this.dataSources = dataSources;
1455
1505
  if (collections) this.registerMultiple(collections);
1456
1506
  }
1507
+ /**
1508
+ * Provide the declared data sources used to resolve each collection's
1509
+ * engine during normalization. Set this before registering collections.
1510
+ * Returns true if the registry changed (callers may re-register).
1511
+ */
1512
+ setDataSources(dataSources) {
1513
+ if ((0, fast_equals.deepEqual)(this.dataSources, dataSources)) return false;
1514
+ this.dataSources = dataSources ?? {};
1515
+ return true;
1516
+ }
1457
1517
  reset() {
1458
1518
  this.collectionsByTableName.clear();
1459
1519
  this.collectionsBySlug.clear();
@@ -1522,6 +1582,10 @@
1522
1582
  }
1523
1583
  normalizeCollection(collection) {
1524
1584
  const result = { ...collection };
1585
+ if (result.dataSource && !result.driver) {
1586
+ const engine = resolveDataSource(result, this.dataSources).engine;
1587
+ if (engine) result.driver = engine;
1588
+ }
1525
1589
  const extractedRelations = this.extractRelationsFromProperties(result.properties);
1526
1590
  const relResult = result;
1527
1591
  const manualRelations = (0, _rebasepro_types.getDataSourceCapabilities)(result.driver).supportsRelations ? relResult.relations ?? [] : [];
@@ -2198,11 +2262,133 @@
2198
2262
  } });
2199
2263
  }
2200
2264
  //#endregion
2265
+ //#region src/data/buildRoutedRebaseData.ts
2266
+ /**
2267
+ * Build a {@link RebaseData} that routes each collection to the right
2268
+ * backend based on its resolved data source.
2269
+ *
2270
+ * `.collection(path)` (and dynamic `data.products`-style access) resolves the
2271
+ * collection's data-source key via `resolveKey` and delegates to the matching
2272
+ * entry in `sources`, falling back to `defaultData` when there is no match.
2273
+ * Because routing keys off the *path being accessed*, a reference widget
2274
+ * inside a Firestore form that points at a Postgres collection is still
2275
+ * served by Postgres — routing follows the target, not the ancestor.
2276
+ *
2277
+ * When `sources` is empty this returns `defaultData` untouched, so the
2278
+ * single-driver setup keeps identical behaviour and identity (important for
2279
+ * effect dependencies that key off the data instance).
2280
+ *
2281
+ * @example
2282
+ * const data = buildRoutedRebaseData({
2283
+ * defaultData: client.data,
2284
+ * sources: { analytics: buildRebaseData(firestoreDriver) },
2285
+ * resolveKey: (path) => resolveDataSource(registry.getCollection(path), defs).key
2286
+ * });
2287
+ * await data.products.find(); // → default (server / Postgres)
2288
+ * await data.events.find(); // → Firestore, if `events.dataSource === "analytics"`
2289
+ */
2290
+ function buildRoutedRebaseData({ defaultData, sources, resolveKey }) {
2291
+ if (!sources || Object.keys(sources).length === 0) return defaultData;
2292
+ function resolve(slugOrPath) {
2293
+ const key = resolveKey(slugOrPath);
2294
+ if (key && sources[key]) return sources[key];
2295
+ return defaultData;
2296
+ }
2297
+ function getAccessor(slugOrPath) {
2298
+ return resolve(slugOrPath).collection(slugOrPath);
2299
+ }
2300
+ return new Proxy({ collection: getAccessor }, { get(_target, prop) {
2301
+ if (prop === "collection") return getAccessor;
2302
+ if (typeof prop === "symbol") return void 0;
2303
+ if (prop === "then" || prop === "toJSON" || prop === "$$typeof") return void 0;
2304
+ return getAccessor((0, _rebasepro_utils.toSnakeCase)(prop));
2305
+ } });
2306
+ }
2307
+ //#endregion
2308
+ //#region src/table-classification.ts
2309
+ /** Schemas that are always considered Rebase-internal. */
2310
+ var REBASE_INTERNAL_SCHEMAS = ["rebase", "auth"];
2311
+ /** Table-name prefixes that mark a table as Rebase-internal regardless of schema. */
2312
+ var REBASE_INTERNAL_PREFIXES = [
2313
+ "_rebase_",
2314
+ "_auth_",
2315
+ "drizzle_"
2316
+ ];
2317
+ /**
2318
+ * Synchronously classify a table based on naming conventions.
2319
+ *
2320
+ * @param tableName - The unqualified name of the table.
2321
+ * @param schemaName - The schema the table belongs to (e.g. `"public"`, `"rebase"`).
2322
+ * @returns `"rebase-internal"` when the table belongs to a reserved schema or
2323
+ * carries a reserved prefix; `"user"` otherwise.
2324
+ *
2325
+ * @remarks
2326
+ * Junction-table detection requires an async database query and is therefore
2327
+ * **not** handled by this function. Use {@link detectJunctionTables} to obtain
2328
+ * the set of junction tables, then reclassify as needed.
2329
+ */
2330
+ function classifyTable(tableName, schemaName) {
2331
+ if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "rebase-internal";
2332
+ return "user";
2333
+ }
2334
+ /**
2335
+ * Convenience predicate that checks whether a table is Rebase-internal.
2336
+ *
2337
+ * @param tableName - The unqualified name of the table.
2338
+ * @param schemaName - The schema the table belongs to.
2339
+ * @returns `true` if the table is classified as `"rebase-internal"`.
2340
+ */
2341
+ function isRebaseInternalTable(tableName, schemaName) {
2342
+ return classifyTable(tableName, schemaName) === "rebase-internal";
2343
+ }
2344
+ /** SQL query that detects junction tables in the `public` schema. */
2345
+ var JUNCTION_TABLES_SQL = `
2346
+ SELECT t.table_name
2347
+ FROM information_schema.tables t
2348
+ WHERE t.table_schema = 'public'
2349
+ AND t.table_type = 'BASE TABLE'
2350
+ AND NOT EXISTS (
2351
+ SELECT 1
2352
+ FROM information_schema.columns c
2353
+ WHERE c.table_schema = t.table_schema
2354
+ AND c.table_name = t.table_name
2355
+ AND c.column_name NOT IN (
2356
+ SELECT kcu.column_name
2357
+ FROM information_schema.key_column_usage kcu
2358
+ JOIN information_schema.table_constraints tc
2359
+ ON tc.constraint_name = kcu.constraint_name
2360
+ AND tc.table_schema = kcu.table_schema
2361
+ WHERE tc.constraint_type = 'FOREIGN KEY'
2362
+ AND kcu.table_schema = t.table_schema
2363
+ AND kcu.table_name = t.table_name
2364
+ )
2365
+ )
2366
+ `;
2367
+ /**
2368
+ * Asynchronously detect junction (link) tables in the `public` schema.
2369
+ *
2370
+ * A junction table is defined as a table where **every** column participates in
2371
+ * at least one foreign-key constraint.
2372
+ *
2373
+ * @param executeSql - A callback that executes a raw SQL string and returns the
2374
+ * resulting rows.
2375
+ * @returns A `Set` containing the names of all detected junction tables.
2376
+ */
2377
+ async function detectJunctionTables(executeSql) {
2378
+ const rows = await executeSql(JUNCTION_TABLES_SQL);
2379
+ const junctionTables = /* @__PURE__ */ new Set();
2380
+ for (const row of rows) if (typeof row.table_name === "string") junctionTables.add(row.table_name);
2381
+ return junctionTables;
2382
+ }
2383
+ //#endregion
2201
2384
  exports.COLLECTION_PATH_SEPARATOR = COLLECTION_PATH_SEPARATOR;
2202
2385
  exports.CollectionRegistry = CollectionRegistry;
2203
2386
  exports.DEFAULT_ONE_OF_TYPE = DEFAULT_ONE_OF_TYPE;
2204
2387
  exports.DEFAULT_ONE_OF_VALUE = DEFAULT_ONE_OF_VALUE;
2388
+ exports.JUNCTION_TABLES_SQL = JUNCTION_TABLES_SQL;
2205
2389
  exports.QueryBuilder = QueryBuilder;
2390
+ exports.REBASE_INTERNAL_PREFIXES = REBASE_INTERNAL_PREFIXES;
2391
+ exports.REBASE_INTERNAL_SCHEMAS = REBASE_INTERNAL_SCHEMAS;
2206
2392
  exports.addInitialSlash = addInitialSlash;
2207
2393
  exports.and = and;
2208
2394
  exports.applyPropertyConditions = applyPropertyConditions;
@@ -2217,15 +2403,19 @@
2217
2403
  exports.buildProperty = buildProperty;
2218
2404
  exports.buildPropertyCallbacks = buildPropertyCallbacks;
2219
2405
  exports.buildRebaseData = buildRebaseData;
2406
+ exports.buildRoutedRebaseData = buildRoutedRebaseData;
2220
2407
  exports.canCreateEntity = canCreateEntity;
2221
2408
  exports.canDeleteEntity = canDeleteEntity;
2222
2409
  exports.canEditEntity = canEditEntity;
2223
2410
  exports.canReadCollection = canReadCollection;
2224
2411
  exports.checkOperation = checkOperation;
2412
+ exports.classifyTable = classifyTable;
2225
2413
  exports.cond = cond;
2414
+ exports.createDataSourceRegistry = createDataSourceRegistry;
2226
2415
  exports.createRelationRef = createRelationRef;
2227
2416
  exports.createRelationRefWithData = createRelationRefWithData;
2228
2417
  exports.defaultUsersCollection = defaultUsersCollection;
2418
+ exports.detectJunctionTables = detectJunctionTables;
2229
2419
  exports.enumToObjectEntries = enumToObjectEntries;
2230
2420
  exports.evaluateCondition = evaluateCondition;
2231
2421
  exports.findRelation = findRelation;
@@ -2253,6 +2443,7 @@
2253
2443
  exports.isHidden = isHidden;
2254
2444
  exports.isPropertyBuilder = isPropertyBuilder;
2255
2445
  exports.isReadOnly = isReadOnly;
2446
+ exports.isRebaseInternalTable = isRebaseInternalTable;
2256
2447
  exports.normalizeToEntityRelation = normalizeToEntityRelation;
2257
2448
  exports.or = or;
2258
2449
  exports.registerConditionOperations = registerConditionOperations;
@@ -2262,6 +2453,7 @@
2262
2453
  exports.resolveArrayProperties = resolveArrayProperties;
2263
2454
  exports.resolveCollectionPathIds = resolveCollectionPathIds;
2264
2455
  exports.resolveCollectionRelations = resolveCollectionRelations;
2456
+ exports.resolveDataSource = resolveDataSource;
2265
2457
  exports.resolveDefaultSelectedView = resolveDefaultSelectedView;
2266
2458
  exports.resolveEnumValues = resolveEnumValues;
2267
2459
  exports.resolveProperties = resolveProperties;