@housekit/kit 0.1.16 → 0.1.19

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 (3) hide show
  1. package/README.md +14 -3
  2. package/dist/index.js +36 -12
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -15,6 +15,7 @@ HouseKit CLI brings a modern, streamlined developer experience to the ClickHouse
15
15
 
16
16
  - **Declarative Workflows**: Define your source of truth in TypeScript.
17
17
  - **Automatic Drift Detection**: Compares your code against the live DB schema instantly.
18
+ - **Engine-Aware Diffing**: Normalizes local engine objects vs. remote SQL to avoid false changes.
18
19
  - **Blue-Green Deployments**: Safe, zero-downtime structural changes for Materialized Views and Tables.
19
20
  - **Cluster Awareness**: First-class support for sharded clusters using `{cluster}` macros and sharding keys.
20
21
  - **Zero Runtime Dependencies**: Powered by `jiti` for native TS loading—no pre-compilation or heavy binaries required.
@@ -104,8 +105,14 @@ export default {
104
105
  HouseKit simplifies managing Replicated and Distributed tables across a cluster.
105
106
 
106
107
  ```typescript
107
- // Define a table that lives on a cluster
108
- export const events = defineTable('events', { ... }, {
108
+ import { defineTable, t, Engine } from '@housekit/orm';
109
+
110
+ // Define a table that lives on a cluster (object syntax still supported)
111
+ export const events = defineTable('events', {
112
+ id: t.uuid('id').primaryKey(),
113
+ userId: t.uuid('user_id'),
114
+ createdAt: t.timestamp('created_at').default('now()'),
115
+ }, {
109
116
  engine: Engine.ReplicatedMergeTree(),
110
117
 
111
118
  // High Portability: Using '{cluster}' tells ClickHouse to use the
@@ -116,6 +123,9 @@ export const events = defineTable('events', { ... }, {
116
123
  shardKey: 'user_id',
117
124
  orderBy: 'id'
118
125
  });
126
+
127
+ // Callback syntax is also available when you want presets or composition:
128
+ // defineTable('events', (t) => ({ ... }), { ... })
119
129
  ```
120
130
  When you run `housekit push`, the CLI automatically detects the cluster configuration and executes the `ALTER` or `CREATE` statements across all nodes using the specified macro.
121
131
 
@@ -131,6 +141,7 @@ Have an existing database with 100 tables? Don't write the code by hand.
131
141
  bunx housekit pull --database production
132
142
  ```
133
143
  This will scan your ClickHouse instance and generate a clean, readable `schema.ts` file with all table definitions, engines, and settings preserved.
144
+ Engine strings from ClickHouse are stored as `customEngine` to guarantee a lossless round-trip.
134
145
 
135
146
  ---
136
147
 
@@ -144,4 +155,4 @@ This will scan your ClickHouse instance and generate a clean, readable `schema.t
144
155
 
145
156
  ## License
146
157
 
147
- MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
158
+ MIT © [Pablo Fernandez Ruiz](https://github.com/pablofdezr)
package/dist/index.js CHANGED
@@ -6004,7 +6004,7 @@ function resolveDatabase(config, name) {
6004
6004
  import { detectMaterializedViewDrift, extractMVQuery } from "@housekit/orm";
6005
6005
 
6006
6006
  // src/schema/diff.ts
6007
- import { normalizeHousekitMetadata, upgradeMetadataVersion } from "@housekit/orm";
6007
+ import { normalizeHousekitMetadata, upgradeMetadataVersion, renderEngineSQL } from "@housekit/orm";
6008
6008
  function columnType(col) {
6009
6009
  return col.toSQL();
6010
6010
  }
@@ -6290,9 +6290,20 @@ function diffTable(table, localCols, remote, opts) {
6290
6290
  warnings.push(`${label} differs (remote="${r || "unset"}", local="${l || "unset"}")`);
6291
6291
  }
6292
6292
  };
6293
- compare("engine", localOpts.engine || "MergeTree", remoteOpts.engine, () => {
6294
- destructiveReasons.push("engine change (requires shadow swap)");
6295
- });
6293
+ let localEngineSQL = "MergeTree";
6294
+ if (localOpts.customEngine) {
6295
+ localEngineSQL = localOpts.customEngine;
6296
+ } else if (localOpts.engine) {
6297
+ localEngineSQL = renderEngineSQL(localOpts.engine);
6298
+ }
6299
+ const normalizeEngine = (engine) => engine.replace(/\s+/g, "").replace(/\(\)/g, "").toLowerCase();
6300
+ const localEngineNorm = normalizeEngine(localEngineSQL);
6301
+ const remoteEngineNorm = normalizeEngine(remoteOpts.engine || "MergeTree");
6302
+ if (localEngineNorm !== remoteEngineNorm) {
6303
+ optionChanges.push("engine");
6304
+ destructiveReasons.push(`engine change (local="${localEngineSQL}", remote="${remoteOpts.engine}") (requires shadow swap)`);
6305
+ warnings.push("Engine mismatch requires full table recreation");
6306
+ }
6296
6307
  compare("orderBy", localOpts.orderBy, remoteOpts.orderBy, () => {
6297
6308
  destructiveReasons.push("order by change");
6298
6309
  plan.push(`ALTER TABLE \`${tableName}\` MODIFY ORDER BY (${normalizeClause(localOpts.orderBy)})`);
@@ -7314,7 +7325,11 @@ function extractHousekitMetadata2(comment) {
7314
7325
  }
7315
7326
  return null;
7316
7327
  }
7317
- function buildTableFile(table, columns, format3 = "ts", metadata) {
7328
+ function extractEngineFromCreate(statement) {
7329
+ const match2 = statement.match(/ENGINE\s*=\s*(.+?)(\s+(?:ORDER|PARTITION|PRIMARY|SAMPLE|TTL|SETTINGS|COMMENT)|$)/i);
7330
+ return match2 ? match2[1].trim() : "MergeTree()";
7331
+ }
7332
+ function buildTableFile(table, columns, engineSQL, format3 = "ts", metadata) {
7318
7333
  const mappedColumns = columns.map((col) => ({
7319
7334
  name: col.name,
7320
7335
  comment: col.comment,
@@ -7341,6 +7356,8 @@ function buildTableFile(table, columns, format3 = "ts", metadata) {
7341
7356
  const comment = `// Auto-generated on ${timestamp}
7342
7357
  `;
7343
7358
  const optionLines = [];
7359
+ const escapedEngine = engineSQL.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
7360
+ optionLines.push(` customEngine: "${escapedEngine}"`);
7344
7361
  if (metadata?.version) {
7345
7362
  optionLines.push(` metadataVersion: "${metadata.version}"`);
7346
7363
  } else {
@@ -7359,9 +7376,12 @@ ${optionLines.join(`,
7359
7376
  const exportStatement = `export const ${variableName} = defineTable('${table}', (t) => ({
7360
7377
  ${columnLines}
7361
7378
  })${optionsBlock});`;
7362
- return `${comment}import { t, defineTable, Engine } from '@housekit/orm';
7379
+ const importLine = `import { t, defineTable, Engine } from '@housekit/orm';`;
7380
+ const typeAlias = `
7381
+ `;
7382
+ return `${comment}${importLine}
7363
7383
 
7364
- ${exportStatement}
7384
+ ${exportStatement}${typeAlias}
7365
7385
  `;
7366
7386
  }
7367
7387
  function persistLanguagePreference(configPath, lang) {
@@ -7543,6 +7563,7 @@ async function pullCommand(options) {
7543
7563
  const createParsed = await createResult.json();
7544
7564
  const createRows = Array.isArray(createParsed) ? createParsed : createParsed?.data ?? [];
7545
7565
  const createStatement = Array.isArray(createRows) && createRows.length > 0 ? createRows[0]?.statement || "" : "";
7566
+ const engineSQL = extractEngineFromCreate(createStatement);
7546
7567
  let tableMetadata = null;
7547
7568
  try {
7548
7569
  const commentRes = await client.query({
@@ -7691,7 +7712,7 @@ async function pullCommand(options) {
7691
7712
  comment: commentFromDescribe || commentFromCreate
7692
7713
  };
7693
7714
  });
7694
- const content = buildTableFile(tableName, columns, fileFormat, tableMetadata);
7715
+ const content = buildTableFile(tableName, columns, engineSQL, fileFormat, tableMetadata);
7695
7716
  writeFileSync3(join4(targetDir, `${tableName}.${fileExtension}`), content);
7696
7717
  describeSpinner.stop();
7697
7718
  success(`Wrote schema file for ${quoteName(tableName)}`);
@@ -8764,22 +8785,25 @@ export default {
8764
8785
  mkdirSync4(schemaPath, { recursive: true });
8765
8786
  success(`Created schema directory: ${quoteName(schemaPath)}`);
8766
8787
  const exampleSchemaPath = join7(schemaPath, `logs.${fileType}`);
8788
+ const importLine = `import { t, defineTable, Engine } from '@housekit/orm';`;
8789
+ const typeAliasLine = `
8790
+ `;
8767
8791
  const exampleSchemaContent = `// Example table - This is a sample schema file to demonstrate HouseKit usage
8768
- import { t, defineTable, Engine } from '@housekit/orm';
8792
+ ${importLine}
8769
8793
 
8770
- export const logs = defineTable('logs', {
8794
+ export const logs = defineTable('logs', (t) => ({
8771
8795
  id: t.uuid('id').autoGenerate().primaryKey(),
8772
8796
  message: t.string('message').comment('Log message content'),
8773
8797
  level: t.enum('level', ['info', 'warning', 'error']).default('info').comment('Log severity level'),
8774
8798
  tags: t.array(t.string('tag')).nullable().comment('Array of tags for categorizing logs'),
8775
8799
  metadata: t.json('metadata').nullable().comment('Additional log metadata'),
8776
8800
  createdAt: t.timestamp('created_at').default('now()'),
8777
- }, {
8801
+ }), {
8778
8802
  engine: Engine.MergeTree(),
8779
8803
  orderBy: ['createdAt', 'id'],
8780
8804
  appendOnly: false
8781
8805
  });
8782
- `;
8806
+ ${typeAliasLine}`;
8783
8807
  writeFileSync4(exampleSchemaPath, exampleSchemaContent);
8784
8808
  info(`Created example schema: ${quoteName(exampleSchemaPath)}`);
8785
8809
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@housekit/kit",
3
- "version": "0.1.16",
3
+ "version": "0.1.19",
4
4
  "description": "CLI tool for HouseKit - manage ClickHouse schemas, migrations, and database operations with type-safe workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@clickhouse/client": "^1.14.0",
51
- "@housekit/orm": "^0.1.16",
51
+ "@housekit/orm": "workspace:*",
52
52
  "chalk": "^5.6.2",
53
53
  "cli-table3": "^0.6.5",
54
54
  "commander": "^12.0.0",