@palantir/pack.schema 0.7.0 → 0.8.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/.turbo/turbo-lint.log +5 -5
- package/.turbo/turbo-transpileBrowser.log +1 -1
- package/.turbo/turbo-transpileCjs.log +1 -1
- package/.turbo/turbo-transpileEsm.log +1 -1
- package/.turbo/turbo-transpileTypes.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +7 -0
- package/build/browser/index.js +79 -29
- package/build/browser/index.js.map +1 -1
- package/build/cjs/index.cjs +79 -29
- package/build/cjs/index.cjs.map +1 -1
- package/build/cjs/index.d.cts +23 -11
- package/build/esm/index.js +79 -29
- package/build/esm/index.js.map +1 -1
- package/build/types/defineMigration.d.ts +16 -10
- package/build/types/defineMigration.d.ts.map +1 -1
- package/build/types/defineSchemaUpdate.d.ts +6 -0
- package/build/types/defineSchemaUpdate.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/addFieldOptions.test.ts +27 -105
- package/src/defineMigration.ts +85 -33
- package/src/defineSchemaUpdate.ts +78 -23
package/src/defineMigration.ts
CHANGED
|
@@ -117,9 +117,10 @@ export interface RecordBuilder<T extends Record<string, Type>> {
|
|
|
117
117
|
type: V,
|
|
118
118
|
options?: FieldOptions,
|
|
119
119
|
): RecordBuilder<T & { [k in K]: V }>;
|
|
120
|
-
|
|
120
|
+
deprecateField<K extends keyof T & string>(
|
|
121
121
|
name: K,
|
|
122
|
-
|
|
122
|
+
message?: string,
|
|
123
|
+
): RecordBuilder<T>;
|
|
123
124
|
build(): RecordDef<T>;
|
|
124
125
|
}
|
|
125
126
|
|
|
@@ -174,14 +175,29 @@ class UnionBuilderImpl<S extends UnionVariants> implements UnionBuilder<S> {
|
|
|
174
175
|
*/
|
|
175
176
|
const UPGRADE_OPTIONS_CARRIER = "__pack_upgrade_options__";
|
|
176
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Non-enumerable carrier property attached to a `RecordDef` by
|
|
180
|
+
* `RecordBuilderImpl.build()` when the builder has collected one or more
|
|
181
|
+
* `deprecateField` calls. `applyMigration` reads this and then deletes the
|
|
182
|
+
* property, so the returned `RecordDef` is structurally clean. Maps field name
|
|
183
|
+
* → its deprecation info.
|
|
184
|
+
*/
|
|
185
|
+
const DEPRECATED_FIELDS_CARRIER = "__pack_deprecated_fields__";
|
|
186
|
+
|
|
187
|
+
export interface FieldDeprecation {
|
|
188
|
+
readonly message?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
class RecordBuilderImpl<T extends RecordFields> implements RecordBuilder<T> {
|
|
178
192
|
private readonly name: string;
|
|
179
193
|
private readonly fields: T;
|
|
180
194
|
private readonly upgradeOptions: ReadonlyMap<string, FieldOptions>;
|
|
195
|
+
private readonly deprecatedFields: ReadonlyMap<string, FieldDeprecation>;
|
|
181
196
|
|
|
182
197
|
constructor(
|
|
183
198
|
initialRecordDef: RecordDef<T>,
|
|
184
199
|
upgradeOptions?: ReadonlyMap<string, FieldOptions>,
|
|
200
|
+
deprecatedFields?: ReadonlyMap<string, FieldDeprecation>,
|
|
185
201
|
) {
|
|
186
202
|
this.name = initialRecordDef.name;
|
|
187
203
|
this.fields = { ...initialRecordDef.fields };
|
|
@@ -189,6 +205,7 @@ class RecordBuilderImpl<T extends RecordFields> implements RecordBuilder<T> {
|
|
|
189
205
|
// they are NOT inherited from the prior version's RecordDef, because each
|
|
190
206
|
// version's upgrades describe a specific version transition.
|
|
191
207
|
this.upgradeOptions = new Map<string, FieldOptions>(upgradeOptions ?? []);
|
|
208
|
+
this.deprecatedFields = new Map<string, FieldDeprecation>(deprecatedFields ?? []);
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
// TODO: resolve field value from arg instead
|
|
@@ -212,24 +229,26 @@ class RecordBuilderImpl<T extends RecordFields> implements RecordBuilder<T> {
|
|
|
212
229
|
docs: "",
|
|
213
230
|
},
|
|
214
231
|
next,
|
|
232
|
+
this.deprecatedFields,
|
|
215
233
|
);
|
|
216
234
|
}
|
|
217
235
|
|
|
218
|
-
|
|
236
|
+
deprecateField<K extends keyof T & string>(
|
|
219
237
|
name: K,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const next = new Map(this.
|
|
223
|
-
next.
|
|
238
|
+
message?: string,
|
|
239
|
+
): RecordBuilder<T> {
|
|
240
|
+
const next = new Map(this.deprecatedFields);
|
|
241
|
+
next.set(name, { message });
|
|
224
242
|
return new RecordBuilderImpl(
|
|
225
243
|
{
|
|
226
244
|
type: ModelDefType.RECORD,
|
|
227
245
|
name: this.name,
|
|
228
|
-
fields:
|
|
246
|
+
fields: { ...this.fields },
|
|
229
247
|
docs: "",
|
|
230
248
|
},
|
|
249
|
+
this.upgradeOptions,
|
|
231
250
|
next,
|
|
232
|
-
)
|
|
251
|
+
);
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
build(): RecordDef<T> {
|
|
@@ -247,6 +266,14 @@ class RecordBuilderImpl<T extends RecordFields> implements RecordBuilder<T> {
|
|
|
247
266
|
writable: false,
|
|
248
267
|
});
|
|
249
268
|
}
|
|
269
|
+
if (this.deprecatedFields.size > 0) {
|
|
270
|
+
Object.defineProperty(result, DEPRECATED_FIELDS_CARRIER, {
|
|
271
|
+
value: this.deprecatedFields,
|
|
272
|
+
enumerable: false,
|
|
273
|
+
configurable: true,
|
|
274
|
+
writable: false,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
250
277
|
return result;
|
|
251
278
|
}
|
|
252
279
|
}
|
|
@@ -270,22 +297,29 @@ export type FieldUpgrades = Record<
|
|
|
270
297
|
Record<string, UpgradeFieldOptions<Record<string, unknown>>>
|
|
271
298
|
>;
|
|
272
299
|
|
|
300
|
+
export type FieldDeprecations = Record<
|
|
301
|
+
string,
|
|
302
|
+
Record<string, FieldDeprecation>
|
|
303
|
+
>;
|
|
304
|
+
|
|
273
305
|
export interface MigrationResult<M extends ModelDefs> {
|
|
274
306
|
readonly models: M;
|
|
275
307
|
readonly upgrades?: FieldUpgrades;
|
|
308
|
+
readonly deprecations?: FieldDeprecations;
|
|
276
309
|
}
|
|
277
310
|
|
|
278
311
|
/**
|
|
279
|
-
* Lower-level entry point that returns
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
312
|
+
* Lower-level entry point that returns the merged models plus the per-field
|
|
313
|
+
* metadata collected during the migration callback: `UpgradeFieldOptions` from
|
|
314
|
+
* `addField(name, type, options)` and deprecations from `deprecateField(name)`.
|
|
315
|
+
* Used by `addSchemaUpdate` to fold sugar-form upgrades into a
|
|
316
|
+
* `VersionedSchema`'s migrations map and deprecations into its deprecations map.
|
|
283
317
|
*
|
|
284
|
-
* Each `RecordBuilderImpl.build()` attaches its collected options
|
|
285
|
-
* produced `RecordDef` via
|
|
286
|
-
* walks the merged result, extracts
|
|
287
|
-
* output key (so renames are handled), and strips the
|
|
288
|
-
* `RecordDef`s are structurally clean.
|
|
318
|
+
* Each `RecordBuilderImpl.build()` attaches its collected upgrade options and
|
|
319
|
+
* deprecations to the produced `RecordDef` via two non-enumerable carrier
|
|
320
|
+
* properties; this function walks the merged result, extracts both keyed by the
|
|
321
|
+
* user's chosen output key (so renames are handled), and strips the carriers so
|
|
322
|
+
* the returned `RecordDef`s are structurally clean.
|
|
289
323
|
*/
|
|
290
324
|
export function applyMigration<
|
|
291
325
|
const T extends ModelDefs,
|
|
@@ -298,35 +332,53 @@ export function applyMigration<
|
|
|
298
332
|
const merged = { ...models, ...migration(builders) } as T & S;
|
|
299
333
|
|
|
300
334
|
const upgrades: FieldUpgrades = {};
|
|
335
|
+
const deprecations: FieldDeprecations = {};
|
|
301
336
|
for (const [modelKey, def] of Object.entries(merged)) {
|
|
302
337
|
if (!isRecordDef(def)) continue;
|
|
303
338
|
const carrier = def as unknown as Record<string, unknown>;
|
|
304
339
|
const opts = carrier[UPGRADE_OPTIONS_CARRIER] as
|
|
305
340
|
| ReadonlyMap<string, FieldOptions>
|
|
306
341
|
| undefined;
|
|
307
|
-
if (opts
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
342
|
+
if (opts != null) {
|
|
343
|
+
// Strip the carrier so the returned RecordDef is structurally clean.
|
|
344
|
+
delete carrier[UPGRADE_OPTIONS_CARRIER];
|
|
345
|
+
const fieldEntries: Record<
|
|
346
|
+
string,
|
|
347
|
+
UpgradeFieldOptions<Record<string, unknown>>
|
|
348
|
+
> = {};
|
|
349
|
+
for (const [fieldName, options] of opts) {
|
|
350
|
+
// Only `UpgradeFieldOptions` (which declare `derivedFrom`) flow into the
|
|
351
|
+
// upgrades map; `AdditiveFieldOptions` carry only a literal `default`
|
|
352
|
+
// and have no read-time lens effect.
|
|
353
|
+
if ("derivedFrom" in options) {
|
|
354
|
+
fieldEntries[fieldName] = options;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (Object.keys(fieldEntries).length > 0) {
|
|
358
|
+
upgrades[modelKey] = fieldEntries;
|
|
320
359
|
}
|
|
321
360
|
}
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
|
|
362
|
+
const deprecated = carrier[DEPRECATED_FIELDS_CARRIER] as
|
|
363
|
+
| ReadonlyMap<string, FieldDeprecation>
|
|
364
|
+
| undefined;
|
|
365
|
+
if (deprecated != null) {
|
|
366
|
+
// Strip the carrier so the returned RecordDef is structurally clean.
|
|
367
|
+
delete carrier[DEPRECATED_FIELDS_CARRIER];
|
|
368
|
+
const fieldEntries: Record<string, FieldDeprecation> = {};
|
|
369
|
+
for (const [fieldName, info] of deprecated) {
|
|
370
|
+
fieldEntries[fieldName] = info;
|
|
371
|
+
}
|
|
372
|
+
if (Object.keys(fieldEntries).length > 0) {
|
|
373
|
+
deprecations[modelKey] = fieldEntries;
|
|
374
|
+
}
|
|
324
375
|
}
|
|
325
376
|
}
|
|
326
377
|
|
|
327
378
|
return {
|
|
328
379
|
models: merged,
|
|
329
380
|
upgrades: Object.keys(upgrades).length > 0 ? upgrades : undefined,
|
|
381
|
+
deprecations: Object.keys(deprecations).length > 0 ? deprecations : undefined,
|
|
330
382
|
};
|
|
331
383
|
}
|
|
332
384
|
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { SchemaBuilder } from "./defineMigration.js";
|
|
17
|
+
import type { FieldDeprecation, SchemaBuilder } from "./defineMigration.js";
|
|
18
18
|
import { applyMigration } from "./defineMigration.js";
|
|
19
19
|
import type { ModelDefs } from "./defs.js";
|
|
20
|
+
import { isRecordDef } from "./utils.js";
|
|
20
21
|
|
|
21
22
|
export interface InitialSchema<T extends ModelDefs = ModelDefs> {
|
|
22
23
|
readonly type: "initial";
|
|
@@ -30,12 +31,19 @@ export interface FieldMigration {
|
|
|
30
31
|
|
|
31
32
|
export type VersionMigrations = Record<string, Record<string, FieldMigration>>;
|
|
32
33
|
|
|
34
|
+
export interface FieldDeprecationInfo {
|
|
35
|
+
readonly fromVersion: number;
|
|
36
|
+
readonly message?: string;
|
|
37
|
+
}
|
|
38
|
+
export type VersionDeprecations = Record<string, Record<string, FieldDeprecationInfo>>;
|
|
39
|
+
|
|
33
40
|
export interface VersionedSchema<T extends ModelDefs = ModelDefs> {
|
|
34
41
|
readonly type: "versioned";
|
|
35
42
|
readonly models: T;
|
|
36
43
|
readonly version: number;
|
|
37
44
|
readonly previous: SchemaDefinition;
|
|
38
45
|
readonly migrations?: VersionMigrations;
|
|
46
|
+
readonly deprecations?: VersionDeprecations;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
export type SchemaDefinition<T extends ModelDefs = ModelDefs> =
|
|
@@ -67,18 +75,18 @@ export interface SchemaVersionBuilder<T extends ModelDefs> {
|
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
78
|
+
* Shallow-merge two `[modelKey][fieldName]` maps; `override` wins per field.
|
|
79
|
+
* Used for both migrations (so `withMigrations` supplements `addField` sugar)
|
|
80
|
+
* and deprecations (so each version's deprecations accumulate forward). The
|
|
81
|
+
* maps are combined at both the model and field level.
|
|
74
82
|
*/
|
|
75
|
-
function
|
|
76
|
-
base:
|
|
77
|
-
override:
|
|
78
|
-
):
|
|
83
|
+
function mergeByModelField<V>(
|
|
84
|
+
base: Record<string, Record<string, V>> | undefined,
|
|
85
|
+
override: Record<string, Record<string, V>> | undefined,
|
|
86
|
+
): Record<string, Record<string, V>> | undefined {
|
|
79
87
|
if (base == null) return override;
|
|
80
88
|
if (override == null) return base;
|
|
81
|
-
const result:
|
|
89
|
+
const result: Record<string, Record<string, V>> = { ...base };
|
|
82
90
|
for (const [modelKey, fields] of Object.entries(override)) {
|
|
83
91
|
result[modelKey] = { ...result[modelKey], ...fields };
|
|
84
92
|
}
|
|
@@ -87,7 +95,7 @@ function mergeMigrations(
|
|
|
87
95
|
|
|
88
96
|
/**
|
|
89
97
|
* Drop migration entries that no longer correspond to a field on a record in
|
|
90
|
-
* `models`. Required because `
|
|
98
|
+
* `models`. Required because `mergeByModelField` is monotonic — a later
|
|
91
99
|
* `addSchemaUpdate` that removes or retypes a field would otherwise leave a
|
|
92
100
|
* stale entry in the accumulator. Entries are dropped when:
|
|
93
101
|
* - the model key is no longer present,
|
|
@@ -102,7 +110,7 @@ function pruneMigrations(
|
|
|
102
110
|
const result: VersionMigrations = {};
|
|
103
111
|
for (const [modelKey, fields] of Object.entries(migrations)) {
|
|
104
112
|
const def = models[modelKey];
|
|
105
|
-
if (def
|
|
113
|
+
if (!isRecordDef(def)) continue;
|
|
106
114
|
const recordFields = def.fields;
|
|
107
115
|
const kept: Record<string, FieldMigration> = {};
|
|
108
116
|
for (const [fieldName, migration] of Object.entries(fields)) {
|
|
@@ -117,44 +125,82 @@ function pruneMigrations(
|
|
|
117
125
|
return Object.keys(result).length > 0 ? result : undefined;
|
|
118
126
|
}
|
|
119
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Convert the version-agnostic `FieldDeprecations` collected by `applyMigration`
|
|
130
|
+
* (just `{ message? }`) into `VersionDeprecations` by stamping `fromVersion`.
|
|
131
|
+
*/
|
|
132
|
+
function stampDeprecationVersions(
|
|
133
|
+
deprecations: Record<string, Record<string, FieldDeprecation>> | undefined,
|
|
134
|
+
version: number,
|
|
135
|
+
): VersionDeprecations | undefined {
|
|
136
|
+
if (deprecations == null) return undefined;
|
|
137
|
+
const result: VersionDeprecations = {};
|
|
138
|
+
for (const [modelKey, fields] of Object.entries(deprecations)) {
|
|
139
|
+
const out: Record<string, FieldDeprecationInfo> = {};
|
|
140
|
+
for (const [fieldName, info] of Object.entries(fields)) {
|
|
141
|
+
out[fieldName] = { fromVersion: version, ...info };
|
|
142
|
+
}
|
|
143
|
+
result[modelKey] = out;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
120
148
|
class SchemaVersionBuilderImpl<T extends ModelDefs> implements SchemaVersionBuilder<T> {
|
|
121
149
|
private readonly models: T;
|
|
122
150
|
private readonly version: number;
|
|
123
151
|
private readonly previous: SchemaDefinition;
|
|
124
152
|
private readonly _migrations: VersionMigrations | undefined;
|
|
153
|
+
private readonly _deprecations: VersionDeprecations | undefined;
|
|
125
154
|
|
|
126
155
|
constructor(
|
|
127
156
|
models: T,
|
|
128
157
|
version: number,
|
|
129
158
|
previous: SchemaDefinition,
|
|
130
|
-
migrations
|
|
159
|
+
migrations: VersionMigrations | undefined,
|
|
160
|
+
deprecations?: VersionDeprecations,
|
|
131
161
|
) {
|
|
132
162
|
this.models = models;
|
|
133
163
|
this.version = version;
|
|
134
164
|
this.previous = previous;
|
|
135
165
|
this._migrations = migrations;
|
|
166
|
+
this._deprecations = deprecations;
|
|
136
167
|
}
|
|
137
168
|
|
|
138
169
|
addSchemaUpdate<S extends ModelDefs>(
|
|
139
170
|
update: SchemaUpdate<T, S>,
|
|
140
171
|
): SchemaVersionBuilder<T & S> {
|
|
141
|
-
// applyMigration returns
|
|
142
|
-
//
|
|
143
|
-
const { models, upgrades } = applyMigration(this.models, update.migration);
|
|
144
|
-
const merged =
|
|
172
|
+
// applyMigration returns the merged models, any sugar-form upgrades from
|
|
173
|
+
// `addField(name, type, { derivedFrom })`, and any `deprecateField` calls.
|
|
174
|
+
const { models, upgrades, deprecations } = applyMigration(this.models, update.migration);
|
|
175
|
+
const merged = mergeByModelField(
|
|
145
176
|
this._migrations,
|
|
146
177
|
upgrades as VersionMigrations | undefined,
|
|
147
178
|
);
|
|
148
|
-
// Reconcile against the new models: a later update that
|
|
149
|
-
//
|
|
179
|
+
// Reconcile against the new models: a later update that retypes a field
|
|
180
|
+
// must drop the prior step's stale migration entry.
|
|
150
181
|
const pruned = pruneMigrations(merged, models);
|
|
151
|
-
|
|
182
|
+
// Attach this version to each freshly-deprecated field, then merge forward.
|
|
183
|
+
const thisVersionDeprecations = stampDeprecationVersions(deprecations, this.version);
|
|
184
|
+
const mergedDeprecations = mergeByModelField(this._deprecations, thisVersionDeprecations);
|
|
185
|
+
return new SchemaVersionBuilderImpl(
|
|
186
|
+
models,
|
|
187
|
+
this.version,
|
|
188
|
+
this.previous,
|
|
189
|
+
pruned,
|
|
190
|
+
mergedDeprecations,
|
|
191
|
+
);
|
|
152
192
|
}
|
|
153
193
|
|
|
154
194
|
withMigrations(migrations: VersionMigrations): SchemaVersionBuilder<T> {
|
|
155
|
-
const merged =
|
|
195
|
+
const merged = mergeByModelField(this._migrations, migrations);
|
|
156
196
|
const pruned = pruneMigrations(merged, this.models);
|
|
157
|
-
return new SchemaVersionBuilderImpl(
|
|
197
|
+
return new SchemaVersionBuilderImpl(
|
|
198
|
+
this.models,
|
|
199
|
+
this.version,
|
|
200
|
+
this.previous,
|
|
201
|
+
pruned,
|
|
202
|
+
this._deprecations,
|
|
203
|
+
);
|
|
158
204
|
}
|
|
159
205
|
|
|
160
206
|
build(): VersionedSchema<T> {
|
|
@@ -164,6 +210,7 @@ class SchemaVersionBuilderImpl<T extends ModelDefs> implements SchemaVersionBuil
|
|
|
164
210
|
version: this.version,
|
|
165
211
|
previous: this.previous,
|
|
166
212
|
...(this._migrations != null ? { migrations: this._migrations } : {}),
|
|
213
|
+
...(this._deprecations != null ? { deprecations: this._deprecations } : {}),
|
|
167
214
|
};
|
|
168
215
|
}
|
|
169
216
|
}
|
|
@@ -171,5 +218,13 @@ class SchemaVersionBuilderImpl<T extends ModelDefs> implements SchemaVersionBuil
|
|
|
171
218
|
export function nextSchema<T extends ModelDefs>(
|
|
172
219
|
previous: SchemaDefinition<T>,
|
|
173
220
|
): SchemaVersionBuilder<T> {
|
|
174
|
-
|
|
221
|
+
const version = previous.version + 1;
|
|
222
|
+
const deprecations = "deprecations" in previous ? previous.deprecations : undefined;
|
|
223
|
+
return new SchemaVersionBuilderImpl(
|
|
224
|
+
previous.models,
|
|
225
|
+
version,
|
|
226
|
+
previous,
|
|
227
|
+
undefined,
|
|
228
|
+
deprecations,
|
|
229
|
+
);
|
|
175
230
|
}
|