@everystack/model 0.4.4 → 0.4.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/model.ts +28 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/model",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Declare a table's schema, validation, and authorization once — compiled to migrations, RLS, and handler config",
5
5
  "license": "AGPL-3.0-only",
6
6
  "publishConfig": {
package/src/model.ts CHANGED
@@ -74,6 +74,19 @@ export interface ModelDefinition {
74
74
  * is past the state — a satisfied rename is an inert notice.
75
75
  */
76
76
  renamedFrom?: string;
77
+ /**
78
+ * `movedFrom: 'old_schema'` — a one-shot migration hint, the cross-schema twin
79
+ * of `renamedFrom`. The SAME table name changed `schema` (e.g. `stats` → `curated`);
80
+ * the diff cannot distinguish a move from a CREATE-in-new-schema + an undeclared
81
+ * orphan left behind, so the marker carries the intent and the diff emits one
82
+ * `ALTER TABLE <old_schema>.<table> SET SCHEMA <schema>` (data, indexes, policies,
83
+ * grants, triggers and owned sequences all ride along) instead of CREATE plus a
84
+ * stranded copy. Names the OLD schema; the new schema is this model's `schema`.
85
+ * Remove it once every environment is past the state — a satisfied move is an
86
+ * inert notice. Cannot be combined with `renamedFrom` (declare the move first,
87
+ * the rename in a later increment).
88
+ */
89
+ movedFrom?: string;
77
90
  /**
78
91
  * `true` removes the table from the generic data API — `deriveHandlerConfig`
79
92
  * skips it, so it never enters `exposedTables`. Default `false`. Package-owned
@@ -111,6 +124,8 @@ export interface ModelDescriptor {
111
124
  private: boolean;
112
125
  /** One-shot rename hint: this table used to be named `renamedFrom` (same schema). */
113
126
  renamedFrom?: string;
127
+ /** One-shot move hint: this table used to live in schema `movedFrom` (same name) — emits SET SCHEMA. */
128
+ movedFrom?: string;
114
129
  /** Triggers on this table (compute — reconciled, never migrated; excluded from the base fingerprint). */
115
130
  triggers: TriggerSpec[];
116
131
  /**
@@ -161,6 +176,18 @@ export function defineModel(table: string, def: ModelDefinition): ModelDescripto
161
176
 
162
177
  validateTriggersFor('table', table, def.triggers ?? []);
163
178
 
179
+ // A rename and a move in one increment can't be composed from two independent markers
180
+ // (each names only half the source — old name OR old schema — so the diff would miss the
181
+ // real `old_schema.old_name` table and silently degrade to CREATE + orphan, the exact bug
182
+ // both markers exist to prevent). Forbid the pair; sequence them across two increments.
183
+ if (def.renamedFrom && def.movedFrom) {
184
+ throw new Error(
185
+ `model '${table}': cannot declare both renamedFrom ('${def.renamedFrom}') and movedFrom ('${def.movedFrom}') ` +
186
+ `in one increment — the diff can't locate the source. Apply the move first (movedFrom), then the rename ` +
187
+ `(renamedFrom) in a later increment.`,
188
+ );
189
+ }
190
+
164
191
  return {
165
192
  table,
166
193
  fields: def.fields,
@@ -173,6 +200,7 @@ export function defineModel(table: string, def: ModelDefinition): ModelDescripto
173
200
  schema: def.schema ?? 'public',
174
201
  private: def.private ?? false,
175
202
  ...(def.renamedFrom ? { renamedFrom: def.renamedFrom } : {}),
203
+ ...(def.movedFrom ? { movedFrom: def.movedFrom } : {}),
176
204
  triggers: def.triggers ?? [],
177
205
  };
178
206
  }