@ragestudio/scylla-odm 0.21.0 → 0.22.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/client.d.ts +1 -0
- package/client.js +1 -1
- package/cql_gen/create_table.js +1 -1
- package/migrate/index.d.ts +16 -0
- package/migrate/index.js +7 -0
- package/model/index.d.ts +2 -2
- package/package.js +1 -1
- package/package.json +1 -1
- package/types.d.ts +10 -5
- package/types.js +1 -1
package/client.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ declare class Client {
|
|
|
18
18
|
sync?: boolean;
|
|
19
19
|
}): Promise<void>;
|
|
20
20
|
batch(logged?: boolean): Batch;
|
|
21
|
+
migrate(modelName?: string): Promise<void>;
|
|
21
22
|
shutdown(): Promise<void>;
|
|
22
23
|
private connectWithRetry;
|
|
23
24
|
executeWithRetry<T>(operation: () => Promise<T>, operationName?: string): Promise<T>;
|
package/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"./driver/index.js";import{Model as t}from"./model/index.js";import{Logger as n}from"./logger/index.js";import r from"./utils/loadModels.js";import i from"./utils/buildMapper.js";import a from"./utils/delay.js";import{Batch as o}from"./batch/index.js";import s from"node:path";const{SCYLLA_CONTACT_POINTS:
|
|
1
|
+
import e from"./driver/index.js";import{Model as t}from"./model/index.js";import{Logger as n}from"./logger/index.js";import r from"./utils/loadModels.js";import i from"./utils/buildMapper.js";import a from"./utils/delay.js";import{Batch as o}from"./batch/index.js";import{executeMigration as s,executeResetMigration as c,migrateModel as l,promptMigration as u,promptResetMigration as d}from"./migrate/index.js";import f from"node:path";const{SCYLLA_CONTACT_POINTS:p,SCYLLA_LOCAL_DATA_CENTER:m,SCYLLA_KEYSPACE:h}=process.env;var Client=class{constructor(t={}){if(globalThis.__scylla_client)throw Error(`An instance of Scylla Client is already initialized`);this.config={modelsPath:f.resolve(process.cwd(),`db`),contactPoints:t.contactPoints??p?.split(`,`)??[`127.0.0.1`],localDataCenter:t.localDataCenter??m??`datacenter1`,keyspace:t.keyspace??h??`default_keyspace`,port:9042,maxRetries:3,retryDelay:1e3,...t};let n={contactPoints:this.config.contactPoints,localDataCenter:this.config.localDataCenter,keyspace:this.config.keyspace,protocolOptions:{port:this.config.port}};this.config.pooling&&(n.pooling=this.config.pooling),this.driver=new e.Client(n)}config;driver;mapper;models=new Map;model(e){return this.models.get(e)}logger=new n;async initialize(n={}){let a;try{a=await r(this.config.modelsPath)}catch(e){throw Error(`Failed to load models: ${e.message}`)}a=a.filter(e=>e instanceof t),this.mapper=new e.mapping.Mapper(this.driver,{models:i(a)}),globalThis.__scylla_client=this,this.logger.log(`Connecting...`),await this.connectWithRetry(),this.logger.log(`Connected`);for(let e of a)this.models.set(e.name,e),n?.sync===!0&&await e._sync()}batch(e=!0){return new o(this,e)}async migrate(e){if(!this.models.size)throw Error(`no models loaded, call initialize() first`);let t;if(e){let n=this.models.get(e);if(!n)throw Error(`model "${e}" not found, available: [${[...this.models.keys()].join(`, `)}]`);t=[n]}else t=[...this.models.values()];for(let e of t){let t=await l(e);if(t.type===`none`){this.logger.log(`[${e.name}] schema is up to date, nothing to migrate`);continue}if(t.errors.length){await d(e.name,t)?(await c(e,t),this.logger.log(`[${e.name}] table reset successfully`)):this.logger.log(`[${e.name}] migration skipped`);continue}await u(e.name,t)?(await s(e,t),this.logger.log(`[${e.name}] migration applied successfully`)):this.logger.log(`[${e.name}] migration skipped`)}}async shutdown(){try{await this.driver.shutdown(),this.logger.log(`connection closed`),delete globalThis.__scylla_client}catch(e){throw this.logger.error(`error shutting down connection:`,e),e}}async connectWithRetry(){let e=null;for(let t=1;t<=this.config.maxRetries;t++)try{await this.driver.connect();return}catch(n){e=n,this.logger.warn(`Connection attempt ${t} failed: ${n.message}`),t<this.config.maxRetries&&(this.logger.log(`Retrying in ${this.config.retryDelay}ms...`),await a(this.config.retryDelay))}throw Error(`Failed to connect to ScyllaDB after ${this.config.maxRetries} attempts: ${e?.message}`)}async executeWithRetry(e,t=`operation`){let n=null;for(let r=1;r<=this.config.maxRetries;r++)try{return await e()}catch(e){if(n=e,this.isRetryableError(e)&&r<this.config.maxRetries){this.logger.warn(`Operation ${t} attempt ${r} failed: ${e.message}`),this.logger.log(`Retrying in ${this.config.retryDelay}ms...`),await a(this.config.retryDelay);continue}throw e}throw Error(`Operation ${t} failed after ${this.config.maxRetries} attempts: ${n?.message}`)}isRetryableError(e){let t=[`timeout`,`connection`,`network`,`unavailable`,`overloaded`,`no hosts available`],n=e.message?.toLowerCase()||``;return t.some(e=>n.includes(e))}};export{Client,Client as default};
|
package/cql_gen/create_table.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function create_table_default(e){let t=e.schema,n=t.table_name,r=e.client.config.keyspace,i=t.fields,a=t.keys,o=t.clustering_order,s=``;for(let e in i){let t=i[e],r=typeof t==`string`?t:t?.type;if(!r)throw Error(`Invalid field type for "${e}" in model "${n}"`);s+=`"${e}" ${r.toUpperCase()}, `}let c=``;if(typeof a==`string`)c=`"${a}"`;else if(Array.isArray(a)&&a.length>0){let e=a[0];c=Array.isArray(e)?`(${e.map(e=>`"${e}"`).join(`, `)})`:`"${e}"`;for(let e=1;e<a.length;e++)c+=`, "${a[e]}"`}else throw Error(`Missing or invalid primary key in model "${n}"`);let l=``;if(o){let e=``;for(let t in o)e!==``&&(e+=`, `),e+=`"${t}" ${o[t].toUpperCase()}`;e!==``&&(l=` WITH CLUSTERING ORDER BY (${e})`)}return`CREATE TABLE IF NOT EXISTS ${r}.${n} (${s}PRIMARY KEY (${c}))${l}`}export{create_table_default as default};
|
|
1
|
+
function create_table_default(e){let t=e.schema,n=t.table_name,r=e.client.config.keyspace,i=t.fields,a=t.keys,o=t.clustering_order,s=``;for(let e in i){let t=i[e],r=typeof t==`string`?t:t?.type;if(!r)throw Error(`Invalid field type for "${e}" in model "${n}"`);s+=`"${e}" ${r.toUpperCase()}, `}let c=``;if(typeof a==`string`)c=`"${a}"`;else if(Array.isArray(a)&&a.length>0){let e=a[0];c=Array.isArray(e)?`(${e.map(e=>`"${String(e)}"`).join(`, `)})`:`"${String(e)}"`;for(let e=1;e<a.length;e++)c+=`, "${String(a[e])}"`}else throw Error(`Missing or invalid primary key in model "${n}"`);let l=``;if(o){let e=``;for(let t in o)e!==``&&(e+=`, `),e+=`"${t}" ${o[t].toUpperCase()}`;e!==``&&(l=` WITH CLUSTERING ORDER BY (${e})`)}return`CREATE TABLE IF NOT EXISTS ${r}.${n} (${s}PRIMARY KEY (${c}))${l}`}export{create_table_default as default};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Model } from "../model/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/migrate/index.d.ts
|
|
4
|
+
interface MigrationResult {
|
|
5
|
+
type: "create" | "alter" | "none";
|
|
6
|
+
cql: string[];
|
|
7
|
+
errors: string[];
|
|
8
|
+
createCql?: string;
|
|
9
|
+
}
|
|
10
|
+
declare function migrateModel(model: Model): Promise<MigrationResult>;
|
|
11
|
+
declare function promptMigration(modelName: string, result: MigrationResult): Promise<boolean>;
|
|
12
|
+
declare function promptResetMigration(modelName: string, result: MigrationResult): Promise<boolean>;
|
|
13
|
+
declare function executeMigration(model: Model, result: MigrationResult): Promise<void>;
|
|
14
|
+
declare function executeResetMigration(model: Model, result: MigrationResult): Promise<void>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { executeMigration, executeResetMigration, migrateModel, promptMigration, promptResetMigration };
|
package/migrate/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import e from"../cql_gen/create_table.js";import*as t from"node:readline";function normalizeType(e){return e.trim().toLowerCase().replace(/\s+/g,` `).replace(/,\s*/g,`, `)}async function getExistingSchema(e,t,n){let r=await e.driver.execute(`
|
|
2
|
+
SELECT column_name, kind, position, type, clustering_order
|
|
3
|
+
FROM system_schema.columns
|
|
4
|
+
WHERE keyspace_name = ? AND table_name = ?
|
|
5
|
+
`,[t,n],{prepare:!0});if(r.rows.length===0)return null;let i=new Map;for(let e of r.rows)i.set(e.column_name,{name:e.column_name,kind:e.kind,position:e.position,type:normalizeType(e.type),clustering_order:e.clustering_order||`none`});let a=[],o=[],s=new Map;for(let e of i.values())e.kind===`partition_key`?a.push(e.name):e.kind===`clustering`&&(o.push(e.name),e.clustering_order!==`none`&&s.set(e.name,e.clustering_order));return a.sort((e,t)=>(i.get(e)?.position??0)-(i.get(t)?.position??0)),o.sort((e,t)=>(i.get(e)?.position??0)-(i.get(t)?.position??0)),{columns:i,partitionKeys:a,clusteringKeys:o,clusteringOrder:s}}function getModelSchema(e){let t=e.schema.fields,n=e.schema.keys,r=e.schema.clustering_order,i=new Map;for(let e in t){let n=t[e],r=typeof n==`string`?n:n?.type||`text`;i.set(e,{type:normalizeType(r),originalType:r})}let a=[],o=[];if(typeof n==`string`)a.push(n);else if(Array.isArray(n)&&n.length>0){let e=n[0];if(Array.isArray(e))for(let t of e)a.push(String(t));else a.push(String(e));for(let e=1;e<n.length;e++)o.push(String(n[e]))}let s=new Map;if(r)for(let e in r)s.set(e,(r[e]||`asc`).toLowerCase());return{columns:i,partitionKeys:a,clusteringKeys:o,clusteringOrder:s}}async function migrateModel(t){let n=t.client,r=n.config.keyspace,i=t.schema.table_name,a=await getExistingSchema(n,r,i);if(!a)return{type:`create`,cql:[e(t)],errors:[]};let o=getModelSchema(t),s=[],c=[],l=a.partitionKeys,u=o.partitionKeys;JSON.stringify(l)!==JSON.stringify(u)&&s.push(`partition key mismatch: existing [${l.join(`, `)}] but model has [${u.join(`, `)}], cannot alter primary key`);let d=a.clusteringKeys,f=o.clusteringKeys;if(JSON.stringify(d)!==JSON.stringify(f)&&s.push(`clustering key mismatch: existing [${d.join(`, `)}] but model has [${f.join(`, `)}], cannot alter clustering keys`),!s.length)for(let e of f){let t=a.clusteringOrder.get(e)||`asc`,n=o.clusteringOrder.get(e)||`asc`;t!==n&&s.push(`clustering order mismatch for column "${e}": existing [${t}] but model has [${n}], cannot alter clustering order`)}for(let[e,t]of o.columns){let n=a.columns.get(e);n&&n.type!==t.type&&s.push(`type mismatch for column "${e}": existing [${n.type}] but model has [${t.type}], cannot alter column type`)}if(s.length)return{type:`alter`,cql:[],errors:s,createCql:e(t)};for(let[e,t]of o.columns)a.columns.has(e)||c.push(`ALTER TABLE ${r}.${i} ADD "${e}" ${t.originalType.toUpperCase()}`);for(let[e]of a.columns)if(!o.columns.has(e)){if(l.includes(e)||d.includes(e)){s.push(`cannot drop primary/clustering key column "${e}"`);continue}c.push(`ALTER TABLE ${r}.${i} DROP "${e}"`)}return s.length?{type:`alter`,cql:[],errors:s,createCql:e(t)}:c.length===0?{type:`none`,cql:[],errors:[]}:{type:`alter`,cql:c,errors:[]}}function ask(e,t){return new Promise(n=>{e.question(t,e=>{n(e.trim())})})}async function promptMigration(e,n){if(n.type===`none`)return!1;let r=n.type===`create`?`CREATE`:`ALTER`;console.log(`\n[${e}] ${r} migration:`);for(let e of n.cql)console.log(` ${e}`);let i=t.createInterface({input:process.stdin,output:process.stdout}),a=await ask(i,`
|
|
6
|
+
Apply this migration? [y/N]: `);return i.close(),a.toLowerCase()===`y`}async function promptResetMigration(e,n){if(!n.errors.length)return!1;console.log(`\n[${e}] alter migration is not possible:`);for(let e of n.errors)console.log(` - ${e}`);n.createCql&&console.log(`\nnew table would be:\n ${n.createCql}`);let r=t.createInterface({input:process.stdin,output:process.stdout});if(console.log(`
|
|
7
|
+
⚠ warning: this will drop and recreate the table, all data will be lost.`),(await ask(r,`continue? [y/N]: `)).toLowerCase()!==`y`)return r.close(),!1;let i=await ask(r,`are you absolutely sure? this action is irreversible. type 'yes' to confirm: `);return r.close(),i===`yes`}async function executeMigration(e,t){for(let n of t.cql)await e.client.driver.execute(n)}async function executeResetMigration(e,t){let n=e.client.config.keyspace,r=e.schema.table_name;await e.client.driver.execute(`DROP TABLE IF EXISTS ${n}.${r}`),t.createCql&&await e.client.driver.execute(t.createCql)}export{executeMigration,executeResetMigration,migrateModel,promptMigration,promptResetMigration};
|
package/model/index.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ declare class Model<TSchema extends Schema<any> = Schema<any>, TDoc = InferDoc<T
|
|
|
13
13
|
get client(): Client;
|
|
14
14
|
get mapper(): mapping.ModelMapper;
|
|
15
15
|
constructor(name: string, schema: TSchema);
|
|
16
|
-
create: (data:
|
|
17
|
-
obj: (data:
|
|
16
|
+
create: (data: TDoc) => Doc<TDoc>;
|
|
17
|
+
obj: (data: TDoc) => Doc<TDoc>;
|
|
18
18
|
batch: {
|
|
19
19
|
update: (batch: Batch, query: Query<TDoc>, options?: UpdateQueryOptions<TDoc>) => void;
|
|
20
20
|
insert: (batch: Batch, query: Query<TDoc>, options?: InsertQueryOptions<TDoc>) => void;
|
package/package.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=`0.
|
|
1
|
+
var e=`0.22.0`,t=`An ODM for ScyllaDB/Cassandra`;export{t as description,e as version};
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -47,10 +47,14 @@ declare enum ColumnTypes {
|
|
|
47
47
|
}
|
|
48
48
|
type TableKeys<T> = (keyof T | TableKeys<T>)[];
|
|
49
49
|
type TableClusteringOrder<T> = Partial<Record<keyof T, "asc" | "desc">>;
|
|
50
|
-
interface Column<T> {
|
|
51
|
-
type?: ColumnTypes | string;
|
|
52
|
-
required?:
|
|
50
|
+
interface Column<T, Required extends boolean = false> {
|
|
51
|
+
type?: ColumnTypes[] | string;
|
|
52
|
+
required?: Required;
|
|
53
53
|
}
|
|
54
|
+
declare const defineColumn: <T>() => <R extends boolean = false>(config: {
|
|
55
|
+
type: any;
|
|
56
|
+
required?: R;
|
|
57
|
+
}) => Column<T, R>;
|
|
54
58
|
type QueryOperators<T> = {
|
|
55
59
|
$eq?: T;
|
|
56
60
|
$ne?: T;
|
|
@@ -82,7 +86,8 @@ type DeleteQueryOptions<T> = {
|
|
|
82
86
|
raw?: boolean;
|
|
83
87
|
when?: { [K in keyof T]?: any };
|
|
84
88
|
} & mapping.RemoveDocInfo;
|
|
89
|
+
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
85
90
|
type Doc<TDoc = any> = Document<TDoc> & TDoc;
|
|
86
|
-
type InferDoc<S> = S extends Schema<infer T> ? { [K in keyof T as K extends `$${string}` ? never : K]: T[K] extends Column<infer U> ? U : T[K] } : any;
|
|
91
|
+
type InferDoc<S> = S extends Schema<infer T> ? Prettify<{ [K in keyof T as K extends `$${string}` ? never : T[K] extends Column<any, true> ? K : never]: T[K] extends Column<infer U, any> ? U : T[K] } & { [K in keyof T as K extends `$${string}` ? never : T[K] extends Column<any, true> ? never : K]?: T[K] extends Column<infer U, any> ? U : T[K] }> : any;
|
|
87
92
|
//#endregion
|
|
88
|
-
export { ClientConfig, Column, ColumnTypes, DeleteQueryOptions, Doc, FindQueryOptions, InferDoc, InsertQueryOptions, Query, QueryOperators, TableClusteringOrder, TableKeys, UpdateQueryOptions };
|
|
93
|
+
export { ClientConfig, Column, ColumnTypes, DeleteQueryOptions, Doc, FindQueryOptions, InferDoc, InsertQueryOptions, Query, QueryOperators, TableClusteringOrder, TableKeys, UpdateQueryOptions, defineColumn };
|
package/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e=function(e){return e.Ascii=`ascii`,e.Bigint=`bigint`,e.Blob=`blob`,e.Boolean=`boolean`,e.Counter=`counter`,e.Date=`date`,e.Decimal=`decimal`,e.Double=`double`,e.Duration=`duration`,e.Float=`float`,e.Frozen=`frozen`,e.Inet=`inet`,e.Int=`int`,e.List=`list`,e.Map=`map`,e.Set=`set`,e.Smallint=`smallint`,e.Text=`text`,e.Time=`time`,e.Timestamp=`timestamp`,e.Timeuuid=`timeuuid`,e.Tinyint=`tinyint`,e.Tuple=`tuple`,e.Uuid=`uuid`,e.Varchar=`varchar`,e.Varint=`varint`,e}({});export{e as ColumnTypes};
|
|
1
|
+
let e=function(e){return e.Ascii=`ascii`,e.Bigint=`bigint`,e.Blob=`blob`,e.Boolean=`boolean`,e.Counter=`counter`,e.Date=`date`,e.Decimal=`decimal`,e.Double=`double`,e.Duration=`duration`,e.Float=`float`,e.Frozen=`frozen`,e.Inet=`inet`,e.Int=`int`,e.List=`list`,e.Map=`map`,e.Set=`set`,e.Smallint=`smallint`,e.Text=`text`,e.Time=`time`,e.Timestamp=`timestamp`,e.Timeuuid=`timeuuid`,e.Tinyint=`tinyint`,e.Tuple=`tuple`,e.Uuid=`uuid`,e.Varchar=`varchar`,e.Varint=`varint`,e}({});const defineColumn=()=>e=>e;export{e as ColumnTypes,defineColumn};
|