@rebasepro/common 0.14.1 → 0.14.2-canary.gca521e9

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/common",
3
3
  "type": "module",
4
- "version": "0.14.1",
4
+ "version": "0.14.2-canary.gca521e9",
5
5
  "description": "Rebase shared core — collection registry, data driver adapter and fluent query builder. No React dependency.",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -40,8 +40,8 @@
40
40
  "dependencies": {
41
41
  "fast-equals": "6.0.2",
42
42
  "json-logic-js": "^2.0.5",
43
- "@rebasepro/types": "0.14.1",
44
- "@rebasepro/utils": "0.14.1"
43
+ "@rebasepro/types": "0.14.2-canary.gca521e9",
44
+ "@rebasepro/utils": "0.14.2-canary.gca521e9"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@jest/globals": "^30.4.1",
@@ -46,7 +46,14 @@ export function resolveRelation(
46
46
 
47
47
  const shared: Pick<ResolvedRelation, "relationName" | "target" | "targetSlug" | "onUpdate" | "onDelete" | "overrides" | "validation"> = {
48
48
  relationName,
49
- target,
49
+ // Normalised, not the thunk as written. Resolution reads the target once
50
+ // and every later consumer calls it again — the driver building a join,
51
+ // the DDL and policy generators, the admin's relation fields — so
52
+ // handing back the raw thunk would give all of them the module namespace
53
+ // `callTarget` just looked past, and the fix would hold only for the
54
+ // fields resolution happens to read here. Still lazy: same call at the
55
+ // same moment, one unwrap on the way out.
56
+ target: () => unwrapModuleNamespace(target()) as CollectionConfig,
50
57
  targetSlug: targetCollection.slug,
51
58
  onUpdate: relation.onUpdate,
52
59
  onDelete: relation.onDelete,
@@ -139,25 +146,58 @@ function describe(relation: Relation, sourceCollection: CollectionConfig, proper
139
146
  }
140
147
 
141
148
  /**
142
- * Call the `target` thunk, and translate the two ways an import cycle breaks it
143
- * into an error that names the cause.
149
+ * A module namespace, unwrapped to the collection it exports.
150
+ *
151
+ * A cycle transpiled to CommonJS does not hand the importing module the
152
+ * *default export* — it hands it the module object, `{ __esModule: true,
153
+ * default: … }`, captured before the exporting module finished evaluating. The
154
+ * `default` slot fills in later, so by the time a lazy `target` thunk runs the
155
+ * collection is sitting right there, one level down. Returning the namespace is
156
+ * never a thing a thunk means to do, and there is exactly one reading of it.
157
+ *
158
+ * Only unwrapped when the inner value is itself a collection: a `default` that
159
+ * is not one is a genuinely wrong thunk, and it should reach the error below
160
+ * rather than be quietly swapped in.
161
+ */
162
+ function unwrapModuleNamespace(value: unknown): unknown {
163
+ if (!value || typeof value !== "object") return value;
164
+ if ((value as { slug?: unknown }).slug) return value;
165
+ const inner = (value as { default?: unknown }).default;
166
+ return inner && typeof inner === "object" && (inner as { slug?: unknown }).slug ? inner : value;
167
+ }
168
+
169
+ /**
170
+ * Call the `target` thunk, and translate the ways an import cycle breaks it into
171
+ * an error that names the cause — or, where the value is recoverable, into the
172
+ * collection the thunk meant.
144
173
  *
145
174
  * The thunk exists to defer the reference until every module has finished
146
- * evaluating, and for a cycle that closes at import time it does. What it cannot
147
- * defer is a cycle that leaves the binding permanently unusable, and there are
148
- * two shapes of that:
175
+ * evaluating, and for a cycle that closes at import time it does. Two cycles
176
+ * leave the binding permanently unusable:
149
177
  *
150
178
  * - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are
151
179
  * in the temporal dead zone, so reading one throws `ReferenceError: x is not
152
180
  * defined`. The stack points at the thunk — a one-line arrow function that is
153
181
  * obviously fine — and says nothing about the cycle that made it throw.
154
- * - **CJS interop.** The half-initialised module object has no `default` yet,
155
- * the import resolves to `undefined`, and the thunk returns it without
156
- * complaint. That one used to surface here as "did not resolve to a
182
+ * - **CJS interop, unresolved.** The half-initialised module object has no
183
+ * `default` yet, the import resolves to `undefined`, and the thunk returns it
184
+ * without complaint. That one used to surface here as "did not resolve to a
157
185
  * collection", which is true and unhelpful.
158
186
  *
159
187
  * Both mean the same thing, and the fix for both is the same: break the cycle,
160
188
  * or move the relation into the collection that does not close it.
189
+ *
190
+ * A third shape is *not* an error, and used to be reported as one. A loader that
191
+ * transpiles ESM to CJS — jiti, which is what `rebase generate-sdk` and
192
+ * `rebase build` load collections with — gives the module entered second in a
193
+ * cycle a namespace object rather than the default export, and never replaces it
194
+ * with a live binding. The thunk then returns `{ __esModule: true, default: … }`
195
+ * holding the fully-initialised collection. Native ESM resolves the same thunk
196
+ * to the collection directly, so this was a loader artefact reported as an
197
+ * authoring mistake, and the advice it gave — make the target a lazy thunk — was
198
+ * already satisfied by the code it was rejecting. Bidirectional relations make
199
+ * these cycles unavoidable, and the lazy thunk is this framework's own answer to
200
+ * them, so {@link unwrapModuleNamespace} takes the collection and moves on.
161
201
  */
162
202
  function callTarget(
163
203
  relation: Relation,
@@ -167,7 +207,7 @@ function callTarget(
167
207
  ): ReturnType<Relation["target"]> {
168
208
  let targetCollection: ReturnType<Relation["target"]> | undefined;
169
209
  try {
170
- targetCollection = target();
210
+ targetCollection = unwrapModuleNamespace(target()) as ReturnType<Relation["target"]>;
171
211
  } catch (error) {
172
212
  // A ReferenceError from inside the thunk is a binding that was never
173
213
  // initialised — nothing else in a one-expression arrow can raise one.
@@ -191,7 +231,11 @@ function callTarget(
191
231
  ? "Under CommonJS interop an import cycle resolves the default import to `undefined`, " +
192
232
  "so check whether this collection and its target import each other. Otherwise the thunk " +
193
233
  "is returning the wrong value — it must return the collection itself, not a promise or a module."
194
- : "The thunk must return a collection config with a `slug`.")
234
+ : typeof (targetCollection as { then?: unknown }).then === "function"
235
+ ? "The thunk returned a promise — `target: () => import(\"./other\")` is asynchronous. " +
236
+ "Import the collection at the top of the file and return the binding: " +
237
+ "`target: () => otherCollection`."
238
+ : "The thunk must return a collection config with a `slug`.")
195
239
  );
196
240
  }
197
241