@rebasepro/common 0.12.1-canary.gf5f1d39 → 0.13.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.es.js +170 -37
- package/dist/index.es.js.map +1 -1
- package/dist/util/auth-default-policies.d.ts +18 -0
- package/dist/util/builders.d.ts +1 -20
- package/dist/util/email.d.ts +29 -0
- package/dist/util/identity.d.ts +14 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/policy/sqlToPolicy.d.ts +6 -1
- package/dist/util/string-column-length.d.ts +24 -0
- package/package.json +7 -7
- package/src/data/buildRebaseData.ts +1 -1
- package/src/util/auth-default-policies.ts +22 -0
- package/src/util/builders.ts +0 -50
- package/src/util/email.ts +32 -0
- package/src/util/identity.ts +36 -0
- package/src/util/index.ts +2 -0
- package/src/util/permissions.test.ts +17 -3
- package/src/util/pg-column-to-property.ts +14 -1
- package/src/util/policy/evaluatePolicy.ts +6 -2
- package/src/util/policy/policyToPostgres.ts +9 -3
- package/src/util/policy/sqlToPolicy.ts +10 -3
- package/src/util/resolve-relation.ts +74 -9
- package/src/util/string-column-length.ts +31 -0
|
@@ -37,13 +37,7 @@ export function resolveRelation(
|
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const targetCollection = target
|
|
41
|
-
if (!targetCollection?.slug) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on ` +
|
|
44
|
-
`'${sourceCollection.slug}' has a \`target\` that did not resolve to a collection.`
|
|
45
|
-
);
|
|
46
|
-
}
|
|
40
|
+
const targetCollection = callTarget(relation, sourceCollection, propertyKey, target);
|
|
47
41
|
|
|
48
42
|
// The name is the address: the `include` key, the admin tab, and the
|
|
49
43
|
// segment of a nested path. Declared name wins, then the declaring
|
|
@@ -80,7 +74,8 @@ export function resolveRelation(
|
|
|
80
74
|
cardinality: "one",
|
|
81
75
|
writable: true,
|
|
82
76
|
shared: false,
|
|
83
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
77
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
78
|
+
sourceKey: relation.sourceKey
|
|
84
79
|
};
|
|
85
80
|
|
|
86
81
|
case "hasMany":
|
|
@@ -90,7 +85,11 @@ export function resolveRelation(
|
|
|
90
85
|
cardinality: "many",
|
|
91
86
|
writable: true,
|
|
92
87
|
shared: false,
|
|
93
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
88
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
89
|
+
// Not defaulted: the source's primary key needs the driver's
|
|
90
|
+
// schema to resolve, which resolution does not have. `undefined`
|
|
91
|
+
// means "the primary key" — see `ResolvedHasMany.sourceKey`.
|
|
92
|
+
sourceKey: relation.sourceKey
|
|
94
93
|
};
|
|
95
94
|
|
|
96
95
|
case "manyToMany": {
|
|
@@ -132,3 +131,69 @@ export function resolveRelation(
|
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
}
|
|
134
|
+
|
|
135
|
+
/** How this relation is addressed in an error message, before it has a resolved name. */
|
|
136
|
+
function describe(relation: Relation, sourceCollection: CollectionConfig, propertyKey?: string): string {
|
|
137
|
+
const name = relation.relationName ?? propertyKey;
|
|
138
|
+
return `Relation${name ? ` '${name}'` : ""} on '${sourceCollection.slug}'`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Call the `target` thunk, and translate the two ways an import cycle breaks it
|
|
143
|
+
* into an error that names the cause.
|
|
144
|
+
*
|
|
145
|
+
* 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:
|
|
149
|
+
*
|
|
150
|
+
* - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are
|
|
151
|
+
* in the temporal dead zone, so reading one throws `ReferenceError: x is not
|
|
152
|
+
* defined`. The stack points at the thunk — a one-line arrow function that is
|
|
153
|
+
* 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
|
|
157
|
+
* collection", which is true and unhelpful.
|
|
158
|
+
*
|
|
159
|
+
* Both mean the same thing, and the fix for both is the same: break the cycle,
|
|
160
|
+
* or move the relation into the collection that does not close it.
|
|
161
|
+
*/
|
|
162
|
+
function callTarget(
|
|
163
|
+
relation: Relation,
|
|
164
|
+
sourceCollection: CollectionConfig,
|
|
165
|
+
propertyKey: string | undefined,
|
|
166
|
+
target: Relation["target"]
|
|
167
|
+
): ReturnType<Relation["target"]> {
|
|
168
|
+
let targetCollection: ReturnType<Relation["target"]> | undefined;
|
|
169
|
+
try {
|
|
170
|
+
targetCollection = target();
|
|
171
|
+
} catch (error) {
|
|
172
|
+
// A ReferenceError from inside the thunk is a binding that was never
|
|
173
|
+
// initialised — nothing else in a one-expression arrow can raise one.
|
|
174
|
+
if (error instanceof ReferenceError) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`${describe(relation, sourceCollection, propertyKey)} targets a collection that is not ` +
|
|
177
|
+
`initialized yet — almost always an import cycle between the two collection files. ` +
|
|
178
|
+
`Break the cycle (move the shared piece into a third module, or import the target ` +
|
|
179
|
+
`lazily) so the target's module finishes evaluating before the registry is built.`,
|
|
180
|
+
{ cause: error }
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!targetCollection?.slug) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`${describe(relation, sourceCollection, propertyKey)} has a \`target\` that resolved to ` +
|
|
189
|
+
`${targetCollection === undefined ? "`undefined`" : "something that is not a collection"}. ` +
|
|
190
|
+
(targetCollection === undefined
|
|
191
|
+
? "Under CommonJS interop an import cycle resolves the default import to `undefined`, " +
|
|
192
|
+
"so check whether this collection and its target import each other. Otherwise the thunk " +
|
|
193
|
+
"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`.")
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return targetCollection;
|
|
199
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { StringProperty } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The length a bounded string column is declared with when the property does
|
|
5
|
+
* not say. Historical: it is what the DDL generator hardcoded, kept so that
|
|
6
|
+
* regenerating an existing schema does not silently redefine its columns.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_STRING_COLUMN_LENGTH = 255;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* How wide a `varchar`/`char` column should be for a given property.
|
|
12
|
+
*
|
|
13
|
+
* One definition, three call sites, because they used to disagree. For the same
|
|
14
|
+
* `columnType: "varchar"` property the DDL generator emitted `VARCHAR(255)`
|
|
15
|
+
* while the Drizzle generator emitted a bare `varchar("col")` — which Postgres
|
|
16
|
+
* reads as *unbounded* — so which of the two you ran decided whether the column
|
|
17
|
+
* had a limit at all. Introspection then dropped the length entirely, so reading
|
|
18
|
+
* an existing `character varying(500)` column back and regenerating it produced
|
|
19
|
+
* a `VARCHAR(255)`: a silent narrowing of a column with data already in it.
|
|
20
|
+
*
|
|
21
|
+
* `validation.max` is the property's own statement about how long the value may
|
|
22
|
+
* be, so it is the only sensible source for the column's width — and it keeps
|
|
23
|
+
* the constraint the database enforces in step with the one the app enforces,
|
|
24
|
+
* rather than inventing a second, different limit underneath it.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveStringColumnLength(prop: Pick<StringProperty, "validation">): number {
|
|
27
|
+
const max = prop.validation?.max;
|
|
28
|
+
return typeof max === "number" && Number.isInteger(max) && max > 0
|
|
29
|
+
? max
|
|
30
|
+
: DEFAULT_STRING_COLUMN_LENGTH;
|
|
31
|
+
}
|