@syncular/typegen 0.15.39 → 0.15.42

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 CHANGED
@@ -148,12 +148,20 @@ silently invent a value for them. `baseline`, `migrations check`, and
148
148
  `generate` therefore reject `ALTER TABLE … ADD COLUMN … NOT NULL`, including
149
149
  one with a literal default.
150
150
 
151
- Use an expand/backfill/validate rollout instead: add the trailing column as
151
+ ### Data changes and backfills
152
+
153
+ Migration SQL is schema-only. `UPDATE`, `INSERT`, and `DELETE` do not mutate
154
+ accepted Syncular row payloads and are rejected before their inner SQL is
155
+ parsed, with a diagnostic linking to this rollout contract. Keep the old
156
+ representation available until the new one is proven complete.
157
+
158
+ Use an expand/backfill/validate/retire rollout instead: add the trailing column as
152
159
  nullable; deploy that schema; fill existing rows with versioned,
153
160
  server-authoritative writes under a new idempotency key; then enforce the
154
- required value in host validation. Do not tighten the synced column's
155
- nullability in a later migration. `generate` appends a valid new migration to
156
- the lock, while
161
+ required value in host validation. Validate the backfill and every supported
162
+ client before retiring an old column or table in a later schema version. Do not
163
+ tighten the synced column's nullability in a later migration. `generate`
164
+ appends a valid new migration to the lock, while
157
165
  `generate --check` fails until that updated lock and generated outputs are
158
166
  committed. Drift diagnostics name the locked migration and the first affected
159
167
  table/column without printing SQL or local paths.
package/dist/sql.js CHANGED
@@ -54,6 +54,8 @@ const DEFAULT_CRDT_TYPE = 'yjs-doc';
54
54
  const WORD_START = /[\p{L}_]/u;
55
55
  const WORD_PART = /[\p{L}\p{M}\p{N}_]/u;
56
56
  const DIGIT = /[0-9]/;
57
+ const DATA_MUTATION_STATEMENTS = new Set(['UPDATE', 'INSERT', 'DELETE']);
58
+ const DATA_MUTATION_GUIDANCE = 'Syncular migration SQL is schema-only: retain the old representation, deploy a nullable expansion, backfill through versioned server-authoritative Syncular writes under a new idempotency key, validate future writes, then retire the old representation in a later schema version. See https://syncular.dev/guide-schema/#data-changes-and-backfills';
57
59
  const tableIdentifierSources = new WeakMap();
58
60
  const columnIdentifierSources = new WeakMap();
59
61
  const indexIdentifierSources = new WeakMap();
@@ -139,7 +141,12 @@ function tokenizeStatements(sql, source) {
139
141
  let end = i + 1;
140
142
  while (end < sql.length && WORD_PART.test(sql[end]))
141
143
  end += 1;
142
- current.push({ kind: 'word', text: sql.slice(i, end) });
144
+ const text = sql.slice(i, end);
145
+ current.push({ kind: 'word', text });
146
+ const statementKind = text.toUpperCase();
147
+ if (current.length === 1 && DATA_MUTATION_STATEMENTS.has(statementKind)) {
148
+ throw new TypegenError(source, `${statementKind} data migration is unsupported. ${DATA_MUTATION_GUIDANCE}`);
149
+ }
143
150
  i = end;
144
151
  }
145
152
  else if (DIGIT.test(ch)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/typegen",
3
- "version": "0.15.39",
3
+ "version": "0.15.42",
4
4
  "description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -56,9 +56,9 @@
56
56
  "!dist/**/*.test.d.ts"
57
57
  ],
58
58
  "dependencies": {
59
- "@syncular/core": "0.15.39"
59
+ "@syncular/core": "0.15.42"
60
60
  },
61
61
  "devDependencies": {
62
- "@syncular/server": "0.15.39"
62
+ "@syncular/server": "0.15.42"
63
63
  }
64
64
  }
package/src/sql.ts CHANGED
@@ -73,6 +73,9 @@ interface Token {
73
73
  const WORD_START = /[\p{L}_]/u;
74
74
  const WORD_PART = /[\p{L}\p{M}\p{N}_]/u;
75
75
  const DIGIT = /[0-9]/;
76
+ const DATA_MUTATION_STATEMENTS = new Set(['UPDATE', 'INSERT', 'DELETE']);
77
+ const DATA_MUTATION_GUIDANCE =
78
+ 'Syncular migration SQL is schema-only: retain the old representation, deploy a nullable expansion, backfill through versioned server-authoritative Syncular writes under a new idempotency key, validate future writes, then retire the old representation in a later schema version. See https://syncular.dev/guide-schema/#data-changes-and-backfills';
76
79
 
77
80
  const tableIdentifierSources = new WeakMap<ParsedTable, string>();
78
81
  const columnIdentifierSources = new WeakMap<IrColumn, string>();
@@ -170,7 +173,15 @@ function tokenizeStatements(sql: string, source: string): Token[][] {
170
173
  } else if (WORD_START.test(ch)) {
171
174
  let end = i + 1;
172
175
  while (end < sql.length && WORD_PART.test(sql[end] as string)) end += 1;
173
- current.push({ kind: 'word', text: sql.slice(i, end) });
176
+ const text = sql.slice(i, end);
177
+ current.push({ kind: 'word', text });
178
+ const statementKind = text.toUpperCase();
179
+ if (current.length === 1 && DATA_MUTATION_STATEMENTS.has(statementKind)) {
180
+ throw new TypegenError(
181
+ source,
182
+ `${statementKind} data migration is unsupported. ${DATA_MUTATION_GUIDANCE}`,
183
+ );
184
+ }
174
185
  i = end;
175
186
  } else if (DIGIT.test(ch)) {
176
187
  let end = i + 1;