@rebasepro/common 0.17.3 → 0.18.1
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/README.md +4 -0
- package/dist/collections/CollectionRegistry.d.ts +1 -1
- package/dist/collections/default-collections.d.ts +15 -84
- package/dist/data/buildRebaseData.d.ts +1 -1
- package/dist/data/filter-dialect.d.ts +11 -0
- package/dist/data/sort-dialect.d.ts +15 -3
- package/dist/index.es.js +375 -63
- package/dist/index.es.js.map +1 -1
- package/dist/util/builders.d.ts +69 -24
- package/dist/util/callback-errors.d.ts +77 -0
- package/dist/util/callback-errors.test.d.ts +1 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/policy/evaluatePolicy.d.ts +6 -0
- package/dist/util/relations.d.ts +41 -0
- package/dist/util/table-name.test.d.ts +1 -0
- package/package.json +26 -22
- package/src/collections/CollectionRegistry.ts +0 -485
- package/src/collections/default-collections.ts +0 -109
- package/src/collections/index.ts +0 -2
- package/src/data/buildRebaseData.ts +0 -816
- package/src/data/buildRoutedRebaseData.ts +0 -103
- package/src/data/filter-conditions.ts +0 -46
- package/src/data/filter-dialect.ts +0 -737
- package/src/data/paginate.ts +0 -334
- package/src/data/query_builder.ts +0 -176
- package/src/data/resolveDataSource.ts +0 -135
- package/src/data/sort-dialect.ts +0 -237
- package/src/index.ts +0 -11
- package/src/table-classification.ts +0 -109
- package/src/types/json-logic-js.d.ts +0 -8
- package/src/util/auth-default-policies.ts +0 -215
- package/src/util/builders.ts +0 -82
- package/src/util/callbacks.ts +0 -122
- package/src/util/collections.ts +0 -117
- package/src/util/common.ts +0 -2
- package/src/util/conditions.ts +0 -168
- package/src/util/email.ts +0 -32
- package/src/util/entities.ts +0 -282
- package/src/util/enums.ts +0 -26
- package/src/util/identity.ts +0 -202
- package/src/util/index.ts +0 -21
- package/src/util/internal-tables.test.ts +0 -188
- package/src/util/internal-tables.ts +0 -197
- package/src/util/junction-policies.ts +0 -355
- package/src/util/paths.ts +0 -27
- package/src/util/permissions.test.ts +0 -866
- package/src/util/permissions.ts +0 -206
- package/src/util/pg-column-to-property.ts +0 -377
- package/src/util/policy/evaluatePolicy.ts +0 -194
- package/src/util/policy/index.ts +0 -4
- package/src/util/policy/policyToPostgres.ts +0 -263
- package/src/util/policy/securityRuleToConditions.ts +0 -67
- package/src/util/policy/sqlToPolicy.ts +0 -422
- package/src/util/relations.ts +0 -236
- package/src/util/resolutions.ts +0 -534
- package/src/util/resolve-relation.ts +0 -243
- package/src/util/storage.ts +0 -177
- package/src/util/string-column-length.ts +0 -31
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CollectionConfig,
|
|
3
|
-
Relation,
|
|
4
|
-
ResolvedRelation
|
|
5
|
-
} from "@rebasepro/types";
|
|
6
|
-
import { generateForeignKeyName, toSnakeCase } from "@rebasepro/utils";
|
|
7
|
-
|
|
8
|
-
import { getTableName } from "./relations";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Fill in a relation's defaults.
|
|
12
|
-
*
|
|
13
|
-
* This replaces `sanitizeRelation`, which had to work out *which kind of link
|
|
14
|
-
* you meant* from whichever optional fields happened to be set — 194 lines of
|
|
15
|
-
* it, including a pass that inspected the target collection's own relations to
|
|
16
|
-
* decide whether a `many`/`inverse` pair was a one-to-many or the far side of a
|
|
17
|
-
* many-to-many, wrapped in a `try/catch` that fell through to the wrong answer
|
|
18
|
-
* when it could not tell. Two consumers running that logic at different moments
|
|
19
|
-
* could reach different conclusions about the same relation.
|
|
20
|
-
*
|
|
21
|
-
* With the kind declared there is nothing to work out. What remains is
|
|
22
|
-
* defaulting — a table name, a column name — which is deterministic, depends
|
|
23
|
-
* only on the relation and its two endpoints, and cannot fail. That is why this
|
|
24
|
-
* function returns rather than throws, and why it needs no cache to be
|
|
25
|
-
* consistent.
|
|
26
|
-
*/
|
|
27
|
-
export function resolveRelation(
|
|
28
|
-
relation: Relation,
|
|
29
|
-
sourceCollection: CollectionConfig,
|
|
30
|
-
propertyKey?: string
|
|
31
|
-
): ResolvedRelation {
|
|
32
|
-
const target = relation.target;
|
|
33
|
-
if (typeof target !== "function") {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on ` +
|
|
36
|
-
`'${sourceCollection.slug}' has no \`target\`. Give it a thunk: \`target: () => otherCollection\`.`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const targetCollection = callTarget(relation, sourceCollection, propertyKey, target);
|
|
41
|
-
|
|
42
|
-
// The name is the address: the `include` key, the admin tab, and the
|
|
43
|
-
// segment of a nested path. Declared name wins, then the declaring
|
|
44
|
-
// property's key, then the target's slug.
|
|
45
|
-
const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);
|
|
46
|
-
|
|
47
|
-
const shared: Pick<ResolvedRelation, "relationName" | "target" | "targetSlug" | "onUpdate" | "onDelete" | "overrides" | "validation"> = {
|
|
48
|
-
relationName,
|
|
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,
|
|
57
|
-
targetSlug: targetCollection.slug,
|
|
58
|
-
onUpdate: relation.onUpdate,
|
|
59
|
-
onDelete: relation.onDelete,
|
|
60
|
-
overrides: relation.overrides,
|
|
61
|
-
validation: relation.validation
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
|
|
65
|
-
|
|
66
|
-
switch (relation.kind) {
|
|
67
|
-
case "belongsTo":
|
|
68
|
-
return {
|
|
69
|
-
...shared,
|
|
70
|
-
kind: "belongsTo",
|
|
71
|
-
cardinality: "one",
|
|
72
|
-
writable: true,
|
|
73
|
-
shared: false,
|
|
74
|
-
localKey: relation.localKey ?? generateForeignKeyName(relationName)
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
case "hasOne":
|
|
78
|
-
return {
|
|
79
|
-
...shared,
|
|
80
|
-
kind: "hasOne",
|
|
81
|
-
cardinality: "one",
|
|
82
|
-
writable: true,
|
|
83
|
-
shared: false,
|
|
84
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
85
|
-
sourceKey: relation.sourceKey
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
case "hasMany":
|
|
89
|
-
return {
|
|
90
|
-
...shared,
|
|
91
|
-
kind: "hasMany",
|
|
92
|
-
cardinality: "many",
|
|
93
|
-
writable: true,
|
|
94
|
-
shared: false,
|
|
95
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
96
|
-
// Not defaulted: the source's primary key needs the driver's
|
|
97
|
-
// schema to resolve, which resolution does not have. `undefined`
|
|
98
|
-
// means "the primary key" — see `ResolvedHasMany.sourceKey`.
|
|
99
|
-
sourceKey: relation.sourceKey
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
case "manyToMany": {
|
|
103
|
-
const sourceTable = getTableName(sourceCollection);
|
|
104
|
-
const targetTable = getTableName(targetCollection);
|
|
105
|
-
return {
|
|
106
|
-
...shared,
|
|
107
|
-
kind: "manyToMany",
|
|
108
|
-
cardinality: "many",
|
|
109
|
-
writable: true,
|
|
110
|
-
shared: true,
|
|
111
|
-
through: {
|
|
112
|
-
// Sorted so both sides of the same link derive the same
|
|
113
|
-
// table without having to agree in advance.
|
|
114
|
-
table: relation.through?.table ?? [sourceTable, targetTable].sort().join("_"),
|
|
115
|
-
sourceColumn: relation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
|
|
116
|
-
targetColumn: relation.through?.targetColumn ?? generateForeignKeyName(relationName)
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
case "via":
|
|
122
|
-
return {
|
|
123
|
-
...shared,
|
|
124
|
-
kind: "via",
|
|
125
|
-
cardinality: relation.cardinality,
|
|
126
|
-
writable: false,
|
|
127
|
-
// A join chain reaches rows that other parents reach too, and
|
|
128
|
-
// Rebase does not know which hop, if any, is a link it owns.
|
|
129
|
-
shared: true,
|
|
130
|
-
joinPath: relation.joinPath
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
default: {
|
|
134
|
-
// Exhaustive: a new kind is a compile error here, not a silent
|
|
135
|
-
// fall-through to whatever shape happened to match first.
|
|
136
|
-
const exhaustive: never = relation;
|
|
137
|
-
throw new Error(`Unknown relation kind: ${JSON.stringify(exhaustive)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** How this relation is addressed in an error message, before it has a resolved name. */
|
|
143
|
-
function describe(relation: Relation, sourceCollection: CollectionConfig, propertyKey?: string): string {
|
|
144
|
-
const name = relation.relationName ?? propertyKey;
|
|
145
|
-
return `Relation${name ? ` '${name}'` : ""} on '${sourceCollection.slug}'`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
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.
|
|
173
|
-
*
|
|
174
|
-
* The thunk exists to defer the reference until every module has finished
|
|
175
|
-
* evaluating, and for a cycle that closes at import time it does. Two cycles
|
|
176
|
-
* leave the binding permanently unusable:
|
|
177
|
-
*
|
|
178
|
-
* - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are
|
|
179
|
-
* in the temporal dead zone, so reading one throws `ReferenceError: x is not
|
|
180
|
-
* defined`. The stack points at the thunk — a one-line arrow function that is
|
|
181
|
-
* obviously fine — and says nothing about the cycle that made it throw.
|
|
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
|
|
185
|
-
* collection", which is true and unhelpful.
|
|
186
|
-
*
|
|
187
|
-
* Both mean the same thing, and the fix for both is the same: break the cycle,
|
|
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.
|
|
201
|
-
*/
|
|
202
|
-
function callTarget(
|
|
203
|
-
relation: Relation,
|
|
204
|
-
sourceCollection: CollectionConfig,
|
|
205
|
-
propertyKey: string | undefined,
|
|
206
|
-
target: Relation["target"]
|
|
207
|
-
): ReturnType<Relation["target"]> {
|
|
208
|
-
let targetCollection: ReturnType<Relation["target"]> | undefined;
|
|
209
|
-
try {
|
|
210
|
-
targetCollection = unwrapModuleNamespace(target()) as ReturnType<Relation["target"]>;
|
|
211
|
-
} catch (error) {
|
|
212
|
-
// A ReferenceError from inside the thunk is a binding that was never
|
|
213
|
-
// initialised — nothing else in a one-expression arrow can raise one.
|
|
214
|
-
if (error instanceof ReferenceError) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`${describe(relation, sourceCollection, propertyKey)} targets a collection that is not ` +
|
|
217
|
-
`initialized yet — almost always an import cycle between the two collection files. ` +
|
|
218
|
-
`Break the cycle (move the shared piece into a third module, or import the target ` +
|
|
219
|
-
`lazily) so the target's module finishes evaluating before the registry is built.`,
|
|
220
|
-
{ cause: error }
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
throw error;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (!targetCollection?.slug) {
|
|
227
|
-
throw new Error(
|
|
228
|
-
`${describe(relation, sourceCollection, propertyKey)} has a \`target\` that resolved to ` +
|
|
229
|
-
`${targetCollection === undefined ? "`undefined`" : "something that is not a collection"}. ` +
|
|
230
|
-
(targetCollection === undefined
|
|
231
|
-
? "Under CommonJS interop an import cycle resolves the default import to `undefined`, " +
|
|
232
|
-
"so check whether this collection and its target import each other. Otherwise the thunk " +
|
|
233
|
-
"is returning the wrong value — it must return the collection itself, not a promise or a module."
|
|
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`.")
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return targetCollection;
|
|
243
|
-
}
|
package/src/util/storage.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { ArrayProperty, EntityValues, StorageConfig, StorageSource, StorageSourceRegistry, StringProperty, UploadedFileContext } from "@rebasepro/types";
|
|
2
|
-
import { randomString } from "@rebasepro/utils";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Resolve the {@link StorageSource} to use for a property, given the key
|
|
6
|
-
* referenced by `StorageConfig.storageSource`.
|
|
7
|
-
*
|
|
8
|
-
* Resolution priority:
|
|
9
|
-
* 1. No `sourceKey` → the default source (backward compatible).
|
|
10
|
-
* 2. An explicit {@link StorageSourceRegistry} (e.g. `client.storageRegistry`).
|
|
11
|
-
* 3. A `sources` lookup map (e.g. the `StorageSourcesContext`).
|
|
12
|
-
* 4. Fall back to the default source.
|
|
13
|
-
*
|
|
14
|
-
* Shared by the upload hook, the markdown editor, and the read-only previews
|
|
15
|
-
* so the resolution logic lives in one place.
|
|
16
|
-
*
|
|
17
|
-
* @group Storage
|
|
18
|
-
*/
|
|
19
|
-
export function resolveStorageSource(params: {
|
|
20
|
-
/** Key from `StorageConfig.storageSource`. */
|
|
21
|
-
sourceKey?: string | null;
|
|
22
|
-
/** Built sources keyed by storage-source key (e.g. from context). */
|
|
23
|
-
sources?: Record<string, StorageSource>;
|
|
24
|
-
/** Optional explicit registry — takes precedence over `sources`. */
|
|
25
|
-
registry?: StorageSourceRegistry;
|
|
26
|
-
/** Default source, used when no key is set or the key cannot be resolved. */
|
|
27
|
-
defaultSource: StorageSource;
|
|
28
|
-
}): StorageSource {
|
|
29
|
-
const { sourceKey, sources, registry, defaultSource } = params;
|
|
30
|
-
if (!sourceKey) return defaultSource;
|
|
31
|
-
if (registry) return registry.getOrDefault(sourceKey);
|
|
32
|
-
const fromSources = sources?.[sourceKey];
|
|
33
|
-
if (fromSources) return fromSources;
|
|
34
|
-
return defaultSource;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ResolveFilenameStringParams<M extends Record<string, unknown>> {
|
|
38
|
-
input: string | ((context: UploadedFileContext) => (Promise<string> | string));
|
|
39
|
-
storage: StorageConfig;
|
|
40
|
-
values: EntityValues<M>;
|
|
41
|
-
entityId?: string | number;
|
|
42
|
-
path?: string;
|
|
43
|
-
property: StringProperty | ArrayProperty,
|
|
44
|
-
file: File;
|
|
45
|
-
propertyKey: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function resolveStorageFilenameString<M extends Record<string, unknown>>(
|
|
49
|
-
{
|
|
50
|
-
input,
|
|
51
|
-
storage,
|
|
52
|
-
values,
|
|
53
|
-
entityId,
|
|
54
|
-
path,
|
|
55
|
-
property,
|
|
56
|
-
file,
|
|
57
|
-
propertyKey
|
|
58
|
-
}: ResolveFilenameStringParams<M>): Promise<string> {
|
|
59
|
-
let result;
|
|
60
|
-
|
|
61
|
-
if (typeof input === "function") {
|
|
62
|
-
result = await input({
|
|
63
|
-
path,
|
|
64
|
-
entityId,
|
|
65
|
-
values,
|
|
66
|
-
property,
|
|
67
|
-
file,
|
|
68
|
-
storage,
|
|
69
|
-
propertyKey
|
|
70
|
-
});
|
|
71
|
-
if (!result)
|
|
72
|
-
console.warn("Storage callback returned empty result. Using default name value")
|
|
73
|
-
} else {
|
|
74
|
-
result = replacePlaceholders({
|
|
75
|
-
file,
|
|
76
|
-
input,
|
|
77
|
-
entityId,
|
|
78
|
-
propertyKey,
|
|
79
|
-
path
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!result)
|
|
84
|
-
result = randomString() + "_" + file.name;
|
|
85
|
-
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
interface ResolveStoragePathStringParams<M extends Record<string, unknown>> {
|
|
90
|
-
input: string | ((context: UploadedFileContext) => string);
|
|
91
|
-
storage: StorageConfig;
|
|
92
|
-
values: EntityValues<M>;
|
|
93
|
-
entityId?: string | number;
|
|
94
|
-
path?: string;
|
|
95
|
-
property: StringProperty | ArrayProperty;
|
|
96
|
-
file: File;
|
|
97
|
-
propertyKey: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function resolveStoragePathString<M extends Record<string, unknown>>(
|
|
101
|
-
{
|
|
102
|
-
input,
|
|
103
|
-
storage,
|
|
104
|
-
values,
|
|
105
|
-
entityId,
|
|
106
|
-
path,
|
|
107
|
-
property,
|
|
108
|
-
file,
|
|
109
|
-
propertyKey
|
|
110
|
-
}: ResolveStoragePathStringParams<M>): string {
|
|
111
|
-
let result;
|
|
112
|
-
if (typeof input === "function") {
|
|
113
|
-
result = input({
|
|
114
|
-
path,
|
|
115
|
-
entityId,
|
|
116
|
-
values,
|
|
117
|
-
property,
|
|
118
|
-
file,
|
|
119
|
-
storage,
|
|
120
|
-
propertyKey
|
|
121
|
-
});
|
|
122
|
-
if (!result)
|
|
123
|
-
console.warn("Storage callback returned empty result. Using default name value")
|
|
124
|
-
} else {
|
|
125
|
-
result = replacePlaceholders({
|
|
126
|
-
file,
|
|
127
|
-
input,
|
|
128
|
-
entityId,
|
|
129
|
-
propertyKey,
|
|
130
|
-
path
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!result)
|
|
135
|
-
result = randomString() + "_" + file.name;
|
|
136
|
-
|
|
137
|
-
return result;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface Placeholders {
|
|
141
|
-
file: File;
|
|
142
|
-
input: string;
|
|
143
|
-
entityId?: string | number;
|
|
144
|
-
propertyKey: string;
|
|
145
|
-
path?: string;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function replacePlaceholders({
|
|
149
|
-
file,
|
|
150
|
-
input,
|
|
151
|
-
entityId,
|
|
152
|
-
propertyKey,
|
|
153
|
-
path
|
|
154
|
-
}: Placeholders) {
|
|
155
|
-
const ext = file.name.split(".").pop();
|
|
156
|
-
let result = input
|
|
157
|
-
.replace("{propertyKey}", propertyKey)
|
|
158
|
-
.replace("{rand}", randomString())
|
|
159
|
-
.replace("{file}", file.name)
|
|
160
|
-
.replace("{file.type}", file.type);
|
|
161
|
-
if (entityId) {
|
|
162
|
-
result = result.replace("{entityId}", String(entityId));
|
|
163
|
-
}
|
|
164
|
-
if (path) {
|
|
165
|
-
result = result.replace("{path}", path);
|
|
166
|
-
}
|
|
167
|
-
if (ext) {
|
|
168
|
-
result = result.replace("{file.ext}", ext);
|
|
169
|
-
const name = file.name.replace(`.${ext}`, "");
|
|
170
|
-
result = result.replace("{file.name}", name)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!result)
|
|
174
|
-
result = randomString() + "_" + file.name;
|
|
175
|
-
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
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
|
-
}
|