@beeblock/svelar 0.4.9 → 0.5.1
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 +24 -1
- package/dist/cli/bin.js +214 -56
- package/dist/cli/commands/MakeFactoryCommand.d.ts +17 -0
- package/dist/cli/commands/MakeTestCommand.d.ts +17 -0
- package/dist/cli/commands/NewCommandTemplates.d.ts +5 -0
- package/dist/cli/index.js +48 -48
- package/dist/plugins/PluginInstaller.js +1 -1
- package/dist/plugins/PluginRegistry.js +1 -1
- package/dist/testing/Factory.d.ts +37 -0
- package/dist/testing/assertions.d.ts +19 -0
- package/dist/testing/auth.d.ts +15 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/request.d.ts +20 -0
- package/dist/testing/setup.d.ts +35 -0
- package/package.json +10 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var w=(u=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(u,{get:(s,e)=>(typeof require<"u"?require:s)[e]}):u)(function(u){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+u+'" is not supported')});function m(u,s){let e=Symbol.for(u),n=globalThis;return n[e]||(n[e]=s()),n[e]}var P=class{plugins=new Map;enabledPlugins=new Set;async discover(){let{join:s}=await import("path"),{existsSync:e,readdirSync:n}=await import("fs"),{readFile:c}=await import("fs/promises"),o=[],t=s(process.cwd(),"node_modules");if(!e(t))return o;let g=async(r,d="")=>{if(e(r))try{let p=n(r,{withFileTypes:!0});for(let a of p){if(!a.isDirectory()||a.name.startsWith("."))continue;if(a.name.startsWith("@")){let h=s(r,a.name);await g(h,a.name+"/");continue}let i=d+a.name,l=a.name.startsWith("svelar-");if(!l)continue;let b=s(r,a.name,"package.json");if(e(b))try{let h=await c(b,"utf-8"),f=JSON.parse(h),k=f.keywords?.includes("svelar-plugin");if(!l&&!k)continue;let y={name:f.name||i,version:f.version||"0.0.0",description:f.description||"",packageName:i,installed:!0,enabled:!1,hasConfig:!!f.svelar?.config,hasMigrations:!!f.svelar?.migrations};o.push(y),this.plugins.set(y.name,y)}catch{}}}catch{}};return await g(t),o}enable(s){let e=this.plugins.get(s);if(!e)throw new Error(`Plugin "${s}" not found in registry.`);this.enabledPlugins.add(s),e.enabled=!0}disable(s){this.enabledPlugins.delete(s);let e=this.plugins.get(s);e&&(e.enabled=!1)}isEnabled(s){return this.enabledPlugins.has(s)}list(){return[...this.plugins.values()]}listEnabled(){return[...this.plugins.values()].filter(s=>this.enabledPlugins.has(s.name))}get(s){return this.plugins.get(s)}register(s){this.plugins.set(s.name,s)}},v=m("svelar.pluginRegistry",()=>new P);var M=class{async publish(s,e){let{mkdir:n,copyFile:c}=await import("fs/promises"),{join:o,dirname:t}=await import("path"),{existsSync:g}=await import("fs"),r={configs:[],migrations:[],assets:[]},d=s.publishables?.()||{};for(let[p,a]of Object.entries(d))for(let i of a){if(e?.only&&i.type!==e.only)continue;let l=o(process.cwd(),i.dest),b=t(l);if(!(g(l)&&!e?.force))try{await n(b,{recursive:!0}),await c(i.source,l),i.type==="config"?r.configs.push(l):i.type==="migration"?r.migrations.push(l):i.type==="asset"&&r.assets.push(l)}catch(h){console.warn(`Failed to publish ${i.source} to ${l}:`,h)}}return r}async preview(s){let e={configs:[],migrations:[],assets:[]},n=s.publishables?.()||{};for(let[c,o]of Object.entries(n))for(let t of o){let g=w("path").join(process.cwd(),t.dest);t.type==="config"?e.configs.push(g):t.type==="migration"?e.migrations.push(g):t.type==="asset"&&e.assets.push(g)}return e}},R=m("svelar.pluginPublisher",()=>new M);var x=class{async install(s,e){let{spawn:n}=await import("child_process"),{promisify:c}=await import("util"),{join:o}=await import("path"),{readFile:t}=await import("fs/promises"),{existsSync:g}=await import("fs");try{await this.runNpmInstall(s);let r=v,d=await r.discover(),p;for(let i of d)if(i.packageName===s||i.name===s){p=i;break}if(!p)return{success:!1,pluginName:s,version:"0.0.0",published:null,error:`Plugin not found after installation. Make sure ${s} is a valid Svelar plugin.`};r.enable(p.name);let a=null;if(e?.publish!==!1)try{let i=await this.loadPluginClass(p.packageName);i&&(a=await R.publish(new i))}catch(i){console.warn("Failed to publish plugin assets:",i)}return{success:!0,pluginName:p.name,version:p.version,published:a}}catch(r){return{success:!1,pluginName:s,version:"0.0.0",published:null,error:r?.message??String(r)}}}async uninstall(s){try{let e=v,n=e.get(s);return n?(e.disable(s),await this.runNpmUninstall(n.packageName),!0):!1}catch(e){return console.error("Failed to uninstall plugin:",e),!1}}async runNpmInstall(s){return new Promise((e,n)=>{let{spawn:c}=w("child_process"),o=c("npm",["install",s],{cwd:process.cwd(),stdio:"inherit"});o.on("close",t=>{t===0?e():n(new Error(`npm install exited with code ${t}`))}),o.on("error",n)})}async runNpmUninstall(s){return new Promise((e,n)=>{let{spawn:c}=w("child_process"),o=c("npm",["uninstall",s],{cwd:process.cwd(),stdio:"inherit"});o.on("close",t=>{t===0?e():n(new Error(`npm uninstall exited with code ${t}`))}),o.on("error",n)})}async loadPluginClass(s){try{let e=await import(s);return e.default||Object.values(e)[0]}catch{return null}}},D=m("svelar.pluginInstaller",()=>new x);export{D as PluginInstaller};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function m(c,n){let e=Symbol.for(c),s=globalThis;return s[e]||(s[e]=n()),s[e]}var g=class{plugins=new Map;enabledPlugins=new Set;async discover(){let{join:n}=await import("path"),{existsSync:e,readdirSync:s}=await import("fs"),{readFile:b}=await import("fs/promises"),o=[],u=n(process.cwd(),"node_modules");if(!e(u))return o;let d=async(a,P="")=>{if(e(a))try{let v=s(a,{withFileTypes:!0});for(let t of v){if(!t.isDirectory()||t.name.startsWith("."))continue;if(t.name.startsWith("@")){let r=n(a,t.name);await d(r,t.name+"/");continue}let p=P+t.name,h=t.name.startsWith("svelar-");if(!h)continue;let f=n(a,t.name,"package.json");if(e(f))try{let r=await b(f,"utf-8"),i=JSON.parse(r),y=i.keywords?.includes("svelar-plugin");if(!h&&!y)continue;let l={name:i.name||p,version:i.version||"0.0.0",description:i.description||"",packageName:p,installed:!0,enabled:!1,hasConfig:!!i.svelar?.config,hasMigrations:!!i.svelar?.migrations};o.push(l),this.plugins.set(l.name,l)}catch{}}}catch{}};return await d(u),o}enable(n){let e=this.plugins.get(n);if(!e)throw new Error(`Plugin "${n}" not found in registry.`);this.enabledPlugins.add(n),e.enabled=!0}disable(n){this.enabledPlugins.delete(n);let e=this.plugins.get(n);e&&(e.enabled=!1)}isEnabled(n){return this.enabledPlugins.has(n)}list(){return[...this.plugins.values()]}listEnabled(){return[...this.plugins.values()].filter(n=>this.enabledPlugins.has(n.name))}get(n){return this.plugins.get(n)}register(n){this.plugins.set(n.name,n)}},k=m("svelar.pluginRegistry",()=>new g);export{k as PluginRegistry};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory — Laravel-inspired model factory for tests.
|
|
3
|
+
*
|
|
4
|
+
* Subclass this to define factories for your models. Each factory
|
|
5
|
+
* has a `definition()` method that returns default attributes and
|
|
6
|
+
* a `model()` method that returns the Model class.
|
|
7
|
+
*/
|
|
8
|
+
import type { Model } from '../orm/Model.js';
|
|
9
|
+
type ModelClass<T> = new (...args: any[]) => T;
|
|
10
|
+
export declare abstract class Factory<T extends Model> {
|
|
11
|
+
private static _sequence;
|
|
12
|
+
/** Auto-incrementing counter unique per factory invocation */
|
|
13
|
+
get sequence(): number;
|
|
14
|
+
/** Reset the global sequence counter (useful between tests) */
|
|
15
|
+
static resetSequence(): void;
|
|
16
|
+
/** Return the Model class this factory produces */
|
|
17
|
+
abstract model(): ModelClass<T>;
|
|
18
|
+
/** Return default attribute values for the model */
|
|
19
|
+
abstract definition(): Record<string, any>;
|
|
20
|
+
/**
|
|
21
|
+
* Create a model instance and persist it to the database.
|
|
22
|
+
*/
|
|
23
|
+
create(overrides?: Partial<Record<string, any>>): Promise<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Create multiple model instances and persist them.
|
|
26
|
+
*/
|
|
27
|
+
createMany(count: number, overrides?: Partial<Record<string, any>>): Promise<T[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Make a model instance without persisting it.
|
|
30
|
+
*/
|
|
31
|
+
make(overrides?: Partial<Record<string, any>>): T;
|
|
32
|
+
/**
|
|
33
|
+
* Make multiple model instances without persisting them.
|
|
34
|
+
*/
|
|
35
|
+
makeMany(count: number, overrides?: Partial<Record<string, any>>): T[];
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database assertions for tests.
|
|
3
|
+
*
|
|
4
|
+
* These helpers query the database and throw assertion errors
|
|
5
|
+
* when the expectation is not met, integrating with any test runner.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Assert that a row matching `conditions` exists in the given table.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertDatabaseHas(table: string, conditions: Record<string, any>, connectionName?: string): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Assert that no row matching `conditions` exists in the given table.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertDatabaseMissing(table: string, conditions: Record<string, any>, connectionName?: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Assert that the table has exactly `expected` rows matching
|
|
17
|
+
* optional `conditions`. If conditions is omitted, counts all rows.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertDatabaseCount(table: string, expected: number, conditions?: Record<string, any>, connectionName?: string): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* actingAs — Simulate an authenticated user in tests.
|
|
3
|
+
*
|
|
4
|
+
* Sets `event.locals.user` to the provided user object,
|
|
5
|
+
* mimicking how AuthenticateMiddleware populates it.
|
|
6
|
+
*/
|
|
7
|
+
import { type RequestEventOptions } from './request.js';
|
|
8
|
+
/**
|
|
9
|
+
* Attach a user to the event's locals, simulating authentication.
|
|
10
|
+
*
|
|
11
|
+
* If `event` is not provided, a fresh RequestEvent is created.
|
|
12
|
+
* Returns the event with `locals.user` set.
|
|
13
|
+
*/
|
|
14
|
+
export declare function actingAs(user: any, event?: any): any;
|
|
15
|
+
export declare function actingAs(user: any, options?: RequestEventOptions): any;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Factory } from './Factory.js';
|
|
2
|
+
export { useSvelarTest, refreshDatabase, type UseSvelarTestOptions } from './setup.js';
|
|
3
|
+
export { assertDatabaseHas, assertDatabaseMissing, assertDatabaseCount } from './assertions.js';
|
|
4
|
+
export { actingAs } from './auth.js';
|
|
5
|
+
export { createRequestEvent, type RequestEventOptions } from './request.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var f=class o{static _sequence=0;get sequence(){return++o._sequence}static resetSequence(){o._sequence=0}async create(t={}){let e=this.model(),n={...this.definition(),...t},r=new e;return Object.assign(r,n),await r.save(),r}async createMany(t,e={}){let n=[];for(let r=0;r<t;r++)n.push(await this.create(e));return n}make(t={}){let e=this.model(),n={...this.definition(),...t},r=new e;return Object.assign(r,n),r}makeMany(t,e={}){let n=[];for(let r=0;r<t;r++)n.push(this.make(e));return n}};import{readdirSync as S}from"fs";import{join as v}from"path";import{pathToFileURL as I}from"url";function b(o,t){let e=Symbol.for(o),n=globalThis;return n[e]||(n[e]=t()),n[e]}var T=class{connections=new Map;config=null;defaultName="default";configure(t){this.config=t,this.defaultName=t.default}async connection(t){let e=t??this.defaultName;if(this.connections.has(e))return this.connections.get(e).drizzle;if(!this.config)throw new Error("Database not configured. Call Connection.configure() first, or register DatabaseServiceProvider.");let n=this.config.connections[e];if(!n)throw new Error(`Database connection "${e}" is not defined in configuration.`);let r=await this.createConnection(n);return this.connections.set(e,r),r.drizzle}async rawClient(t){let e=t??this.defaultName;return await this.connection(e),this.connections.get(e).rawClient}async raw(t,e=[],n){let r=await this.connection(n),i=this.getConfig(n);switch(i.driver){case"sqlite":{let s=await this.rawClient(n),l=e.map(m=>typeof m=="boolean"?m?1:0:m instanceof Date?m.toISOString():m),c=s.prepare(t),u=t.trimStart().toUpperCase();return u.startsWith("SELECT")||u.startsWith("PRAGMA")||u.startsWith("WITH")?c.all(...l):c.run(...l)}case"postgres":return(await this.rawClient(n))(t,...e);case"mysql":{let s=await this.rawClient(n),[l]=await s.execute(t,e);return l}default:throw new Error(`Unsupported driver: ${i.driver}`)}}getDriver(t){return this.getConfig(t).driver}getConfig(t){let e=t??this.defaultName;if(!this.config)throw new Error("Database not configured.");let n=this.config.connections[e];if(!n)throw new Error(`Database connection "${e}" is not defined.`);return n}async disconnect(t){if(t){let e=this.connections.get(t);e&&(await this.closeConnection(e),this.connections.delete(t))}else{for(let[e,n]of this.connections)await this.closeConnection(n);this.connections.clear()}}isConnected(t){return this.connections.has(t??this.defaultName)}async transaction(t,e){let n=this.getConfig(e),r=await this.rawClient(e);switch(n.driver){case"sqlite":{r.exec("BEGIN");try{let i=await t();return r.exec("COMMIT"),i}catch(i){throw r.exec("ROLLBACK"),i}}case"postgres":{await r`BEGIN`;try{let i=await t();return await r`COMMIT`,i}catch(i){throw await r`ROLLBACK`,i}}case"mysql":{let i=await r.getConnection();await i.beginTransaction();try{let s=await t();return await i.commit(),i.release(),s}catch(s){throw await i.rollback(),i.release(),s}}default:throw new Error(`Unsupported driver: ${n.driver}`)}}async createConnection(t){switch(t.driver){case"sqlite":return this.createSQLiteConnection(t);case"postgres":return this.createPostgresConnection(t);case"mysql":return this.createMySQLConnection(t);default:throw new Error(`Unsupported database driver: ${t.driver}`)}}async createSQLiteConnection(t){let e=t.filename??t.database??":memory:";try{let n=(await import("better-sqlite3")).default,{drizzle:r}=await import("drizzle-orm/better-sqlite3"),i=new n(e);return i.pragma("journal_mode = WAL"),i.pragma("foreign_keys = ON"),{drizzle:r(i),config:t,rawClient:i}}catch(n){let r;try{r=(await new Function("mod","return import(mod)")("node:sqlite")).DatabaseSync}catch{throw new Error(`No SQLite driver available. Install better-sqlite3 (npm install better-sqlite3) or use Node.js v22+ which includes built-in SQLite support. Original error: ${n instanceof Error?n.message:String(n)}`)}let i=new r(e);i.exec("PRAGMA journal_mode = WAL"),i.exec("PRAGMA foreign_keys = ON");let s={prepare(c){let u=i.prepare(c);return{all(...m){return u.all(...m)},run(...m){return u.run(...m)},get(...m){return u.get(...m)}}},exec(c){i.exec(c)},pragma(c){return i.prepare(`PRAGMA ${c}`).all()},close(){i.close()}},l;try{let{drizzle:c}=await import("drizzle-orm/better-sqlite3");l=c(s)}catch{l=s}return{drizzle:l,config:t,rawClient:s}}}async createPostgresConnection(t){let e=(await import("postgres")).default,{drizzle:n}=await import("drizzle-orm/postgres-js"),r=t.url??`postgres://${t.user}:${t.password}@${t.host??"localhost"}:${t.port??5432}/${t.database}`,i=e(r);return{drizzle:n(i),config:t,rawClient:i}}async createMySQLConnection(t){let e=await import("mysql2/promise"),{drizzle:n}=await import("drizzle-orm/mysql2"),r=e.createPool({host:t.host??"localhost",port:t.port??3306,database:t.database,user:t.user,password:t.password,uri:t.url});return{drizzle:n(r),config:t,rawClient:r}}async closeConnection(t){try{switch(t.config.driver){case"sqlite":t.rawClient.close();break;case"postgres":await t.rawClient.end();break;case"mysql":await t.rawClient.end();break}}catch{}}},a=b("svelar.connection",()=>new T);var h=class{constructor(t){this.column=t}nullable(){return this.column.nullable=!0,this}notNullable(){return this.column.nullable=!1,this}default(t){return this.column.defaultValue=t,this}primary(){return this.column.primaryKey=!0,this}unique(){return this.column.unique=!0,this}unsigned(){return this.column.unsigned=!0,this}references(t,e){return this.column.references={table:e,column:t},new N(this.column)}build(){return this.column}},N=class{constructor(t){this.column=t}onDelete(t){return this.column.references.onDelete=t,this}onUpdate(t){return this.column.references.onUpdate=t,this}},E=class{columns=[];indices=[];compositePrimary=null;addColumn(t,e){let n={name:t,type:e,nullable:!1,primaryKey:!1,autoIncrement:!1,unique:!1,unsigned:!1};return this.columns.push(n),new h(n)}increments(t="id"){let e={name:t,type:"INTEGER",nullable:!1,primaryKey:!0,autoIncrement:!0,unique:!1,unsigned:!0};return this.columns.push(e),new h(e)}bigIncrements(t="id"){let e={name:t,type:"BIGINT",nullable:!1,primaryKey:!0,autoIncrement:!0,unique:!1,unsigned:!0};return this.columns.push(e),new h(e)}string(t,e=255){return this.addColumn(t,`VARCHAR(${e})`)}text(t){return this.addColumn(t,"TEXT")}integer(t){return this.addColumn(t,"INTEGER")}bigInteger(t){return this.addColumn(t,"BIGINT")}float(t){return this.addColumn(t,"FLOAT")}decimal(t,e=8,n=2){return this.addColumn(t,`DECIMAL(${e},${n})`)}boolean(t){return this.addColumn(t,"BOOLEAN")}date(t){return this.addColumn(t,"DATE")}datetime(t){return this.addColumn(t,"DATETIME")}timestamp(t){return this.addColumn(t,"TIMESTAMP")}timestamps(){this.timestamp("created_at").nullable(),this.timestamp("updated_at").nullable()}json(t){return this.addColumn(t,"JSON")}blob(t){return this.addColumn(t,"BLOB")}enum(t,e){return this.addColumn(t,`ENUM(${e.map(n=>`'${n}'`).join(",")})`)}uuid(t="id"){return this.addColumn(t,"UUID")}ulid(t="id"){return this.addColumn(t,"ULID")}jsonb(t){return this.addColumn(t,"JSONB")}primary(t){this.compositePrimary=t}index(t,e){let n=Array.isArray(t)?t:[t];this.indices.push({columns:n,unique:!1,name:e})}uniqueIndex(t,e){let n=Array.isArray(t)?t:[t];this.indices.push({columns:n,unique:!0,name:e})}foreign(t){let e=this.columns.find(n=>n.name===t);if(!e)throw new Error(`Column "${t}" must be defined before adding a foreign key.`);return new h(e)}toSQL(t,e){let n=[],r=[];for(let i of this.columns)r.push(this.columnToSQL(i,e));this.compositePrimary&&r.push(`PRIMARY KEY (${this.compositePrimary.join(", ")})`);for(let i of this.columns)if(i.references){let s=`FOREIGN KEY (${i.name}) REFERENCES ${i.references.table}(${i.references.column})`;i.references.onDelete&&(s+=` ON DELETE ${i.references.onDelete}`),i.references.onUpdate&&(s+=` ON UPDATE ${i.references.onUpdate}`),r.push(s)}n.push(`CREATE TABLE ${t} (
|
|
2
|
+
${r.join(`,
|
|
3
|
+
`)}
|
|
4
|
+
)`);for(let i of this.indices){let s=i.name??`idx_${t}_${i.columns.join("_")}`,l=i.unique?"UNIQUE ":"";n.push(`CREATE ${l}INDEX ${s} ON ${t} (${i.columns.join(", ")})`)}return n}columnToSQL(t,e){let n=t.name,r=t.type;if(e==="sqlite"?r=this.mapSQLiteType(r,t):e==="postgres"?r=this.mapPostgresType(r,t):e==="mysql"&&(r=this.mapMySQLType(r,t)),n+=` ${r}`,t.primaryKey&&!this.compositePrimary&&(n+=" PRIMARY KEY",t.autoIncrement&&(e==="sqlite"?n+=" AUTOINCREMENT":e==="postgres"||e==="mysql"&&(n+=" AUTO_INCREMENT"))),!t.nullable&&!t.primaryKey&&(n+=" NOT NULL"),t.unique&&!t.primaryKey&&(n+=" UNIQUE"),t.defaultValue!==void 0){let i=typeof t.defaultValue=="string"?`'${t.defaultValue}'`:t.defaultValue===null?"NULL":t.defaultValue;n+=` DEFAULT ${i}`}return n}mapSQLiteType(t,e){return t==="BOOLEAN"?"INTEGER":t==="UUID"||t==="ULID"||t.startsWith("ENUM")||t==="JSON"||t==="JSONB"?"TEXT":t==="BIGINT"&&e.autoIncrement?"INTEGER":t}mapPostgresType(t,e){return e.autoIncrement&&t==="INTEGER"?"SERIAL":e.autoIncrement&&t==="BIGINT"?"BIGSERIAL":t==="DATETIME"?"TIMESTAMP":t==="BLOB"?"BYTEA":t.startsWith("ENUM")?"TEXT":t==="UUID"?"UUID":t==="ULID"?"VARCHAR(26)":t==="JSON"||t==="JSONB"?"JSONB":t}mapMySQLType(t,e){return t==="BOOLEAN"?"TINYINT(1)":t==="UUID"?"CHAR(36)":t==="ULID"?"CHAR(26)":t==="JSONB"?"JSON":t==="TIMESTAMP"?"DATETIME":e.unsigned&&!t.startsWith("DECIMAL")?`${t} UNSIGNED`:t}},p=class{constructor(t){this.connectionName=t}async createTable(t,e){let n=new E;e(n);let r=a.getDriver(this.connectionName),i=n.toSQL(t,r);for(let s of i)await a.raw(s,[],this.connectionName)}async dropTable(t){await a.raw(`DROP TABLE IF EXISTS ${t}`,[],this.connectionName)}async dropTableIfExists(t){await a.raw(`DROP TABLE IF EXISTS ${t}`,[],this.connectionName)}async renameTable(t,e){a.getDriver(this.connectionName)==="mysql"?await a.raw(`RENAME TABLE ${t} TO ${e}`,[],this.connectionName):await a.raw(`ALTER TABLE ${t} RENAME TO ${e}`,[],this.connectionName)}async hasTable(t){let e=a.getDriver(this.connectionName),n;switch(e){case"sqlite":n=await a.raw("SELECT name FROM sqlite_master WHERE type='table' AND name=?",[t],this.connectionName);break;case"postgres":n=await a.raw("SELECT tablename FROM pg_tables WHERE tablename = $1",[t],this.connectionName);break;case"mysql":n=await a.raw("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = ?",[t],this.connectionName);break;default:throw new Error(`Unsupported driver: ${e}`)}return n.length>0}async addColumn(t,e){let n=new E;e(n);let r=a.getDriver(this.connectionName),i=n.columns;for(let s of i){let l=n.columnToSQL(s,r);await a.raw(`ALTER TABLE ${t} ADD COLUMN ${l}`,[],this.connectionName)}}async dropColumn(t,e){await a.raw(`ALTER TABLE ${t} DROP COLUMN ${e}`,[],this.connectionName)}},z=b("svelar.schema",()=>new p);var y=class{migrationsTable="svelar_migrations";connectionName;constructor(t){this.connectionName=t}async ensureMigrationsTable(){let t=new p(this.connectionName);await t.hasTable(this.migrationsTable)||await t.createTable(this.migrationsTable,n=>{n.increments("id"),n.string("migration").unique(),n.integer("batch"),n.timestamp("ran_at").default("CURRENT_TIMESTAMP")})}async run(t){await this.ensureMigrationsTable();let e=await this.getRanMigrations(),n=t.filter(s=>!e.includes(s.name));if(n.length===0)return[];let r=await this.getNextBatch(),i=[];for(let s of n)await s.migration.up(),await a.raw(`INSERT INTO ${this.migrationsTable} (migration, batch) VALUES (?, ?)`,[s.name,r],this.connectionName),i.push(s.name);return i}async rollback(t){await this.ensureMigrationsTable();let e=await this.getLastBatch();if(e===0)return[];let n=await a.raw(`SELECT migration FROM ${this.migrationsTable} WHERE batch = ? ORDER BY id DESC`,[e],this.connectionName),r=[];for(let i of n){let s=i.migration,l=t.find(c=>c.name===s);l&&(await l.migration.down(),await a.raw(`DELETE FROM ${this.migrationsTable} WHERE migration = ?`,[s],this.connectionName),r.push(s))}return r}async reset(t){let e=[];for(;;){let n=await this.rollback(t);if(n.length===0)break;e.push(...n)}return e}async refresh(t){let e=await this.reset(t),n=await this.run(t);return{reset:e,migrated:n}}async fresh(t){let e=await this.dropAllTables(),n=await this.run(t);return{dropped:e,migrated:n}}async dropAllTables(){let t=a.getDriver(this.connectionName),e=[];switch(t){case"sqlite":{e=(await a.raw("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",[],this.connectionName)).map(r=>r.name),await a.raw("PRAGMA foreign_keys = OFF",[],this.connectionName);for(let r of e)await a.raw(`DROP TABLE IF EXISTS "${r}"`,[],this.connectionName);await a.raw("PRAGMA foreign_keys = ON",[],this.connectionName);break}case"mysql":{e=(await a.raw("SHOW TABLES",[],this.connectionName)).map(r=>Object.values(r)[0]),await a.raw("SET FOREIGN_KEY_CHECKS = 0",[],this.connectionName);for(let r of e)await a.raw(`DROP TABLE IF EXISTS \`${r}\``,[],this.connectionName);await a.raw("SET FOREIGN_KEY_CHECKS = 1",[],this.connectionName);break}case"postgres":{e=(await a.raw("SELECT tablename FROM pg_tables WHERE schemaname = 'public'",[],this.connectionName)).map(r=>r.tablename);for(let r of e)await a.raw(`DROP TABLE IF EXISTS "${r}" CASCADE`,[],this.connectionName);break}default:throw new Error(`Unsupported driver for fresh: ${t}`)}return e}async getRanMigrations(){try{return(await a.raw(`SELECT migration FROM ${this.migrationsTable} ORDER BY batch, id`,[],this.connectionName)).map(e=>e.migration)}catch{return[]}}async status(t){let e=await this.getRanMigrations(),n=new Map;try{let r=await a.raw(`SELECT migration, batch FROM ${this.migrationsTable}`,[],this.connectionName);for(let i of r)n.set(i.migration,i.batch)}catch{}return t.map(r=>({name:r.name,ran:e.includes(r.name),batch:n.get(r.name)??null}))}async getNextBatch(){return await this.getLastBatch()+1}async getLastBatch(){try{return(await a.raw(`SELECT MAX(batch) as max_batch FROM ${this.migrationsTable}`,[],this.connectionName))[0]?.max_batch??0}catch{return 0}}};function M(o={}){let t=globalThis.beforeAll,e=globalThis.beforeEach,n=globalThis.afterEach;if(!t||!e)throw new Error("useSvelarTest() requires Vitest globals. Set `globals: true` in vitest.config.ts.");t(async()=>{try{a.getDriver()}catch{a.configure({default:"sqlite",connections:{sqlite:{driver:"sqlite",filename:":memory:"}}})}}),e(async()=>{f.resetSequence(),o.refreshDatabase&&await R(o.connectionName)})}async function R(o){let t=new y(o),e=v(process.cwd(),"src","lib","database","migrations"),n;try{n=S(e).filter(i=>i.endsWith(".ts")||i.endsWith(".js")).sort()}catch{return}let r=[];for(let i of n){let s=v(e,i);try{let l=await import(I(s).href),c=l.default??Object.values(l).find(u=>typeof u=="function"&&u.prototype&&typeof u.prototype.up=="function");c&&r.push({name:i.replace(/\.(ts|js)$/,""),timestamp:i.split("_")[0],path:s,migration:new c})}catch{}}await t.fresh(r)}async function L(o,t,e){if(await C(o,t,e)===0){let r=JSON.stringify(t);throw new Error(`assertDatabaseHas failed: expected at least one row in "${o}" matching ${r}, but found none.`)}}async function O(o,t,e){let n=await C(o,t,e);if(n>0){let r=JSON.stringify(t);throw new Error(`assertDatabaseMissing failed: expected no rows in "${o}" matching ${r}, but found ${n}.`)}}async function B(o,t,e,n){let r=e?await C(o,e,n):await C(o,{},n);if(r!==t){let i=e?` matching ${JSON.stringify(e)}`:"";throw new Error(`assertDatabaseCount failed: expected ${t} row(s) in "${o}"${i}, but found ${r}.`)}}async function C(o,t,e){let n=Object.keys(t),r=`SELECT COUNT(*) AS cnt FROM "${o}"`,i=[];if(n.length>0){let l=n.map(c=>(i.push(t[c]),`"${c}" = ?`));r+=` WHERE ${l.join(" AND ")}`}let s=await a.raw(r,i,e);return Number(s[0]?.cnt??0)}function D(o={}){let{method:t="GET",url:e="http://localhost:5173/",headers:n={},body:r,params:i={},locals:s={},cookies:l={}}=o,c=new URL(e,"http://localhost:5173"),u=new Map(Object.entries(n).map(([d,g])=>[d.toLowerCase(),g])),m={method:t,headers:n};r!==void 0&&t!=="GET"&&t!=="HEAD"&&(m.body=typeof r=="string"?r:JSON.stringify(r),u.has("content-type")||u.set("content-type","application/json"));let A=new Request(c.href,m),w=new Map(Object.entries(l));return{request:A,url:c,params:i,locals:{...s},route:{id:c.pathname},isDataRequest:!1,isSubRequest:!1,platform:{},cookies:{get:d=>w.get(d),getAll:()=>Array.from(w.entries()).map(([d,g])=>({name:d,value:g})),set:(d,g)=>{w.set(d,g)},delete:d=>{w.delete(d)},serialize:(d,g)=>`${d}=${g}`},fetch:globalThis.fetch,getClientAddress:()=>"127.0.0.1",setHeaders:()=>{}}}function $(o,t){let e;return t&&typeof t=="object"&&"request"in t?e=t:e=D(t??{}),e.locals.user=o,e}export{f as Factory,$ as actingAs,B as assertDatabaseCount,L as assertDatabaseHas,O as assertDatabaseMissing,D as createRequestEvent,R as refreshDatabase,M as useSvelarTest};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createRequestEvent — Build a mock SvelteKit RequestEvent for testing.
|
|
3
|
+
*
|
|
4
|
+
* This allows testing server-side handlers (load functions, API endpoints,
|
|
5
|
+
* controllers) without a running server.
|
|
6
|
+
*/
|
|
7
|
+
export interface RequestEventOptions {
|
|
8
|
+
method?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: any;
|
|
12
|
+
params?: Record<string, string>;
|
|
13
|
+
locals?: Record<string, any>;
|
|
14
|
+
cookies?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a mock SvelteKit RequestEvent suitable for passing to
|
|
18
|
+
* server handlers, controllers, and middleware.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createRequestEvent(options?: RequestEventOptions): any;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test setup utilities.
|
|
3
|
+
*
|
|
4
|
+
* `useSvelarTest()` wires up beforeEach/afterEach hooks automatically.
|
|
5
|
+
* `refreshDatabase()` drops all tables and re-runs migrations.
|
|
6
|
+
*/
|
|
7
|
+
export interface UseSvelarTestOptions {
|
|
8
|
+
/** When true, calls refreshDatabase() before each test. Default: false */
|
|
9
|
+
refreshDatabase?: boolean;
|
|
10
|
+
/** Database connection name to use. Default: undefined (uses default) */
|
|
11
|
+
connectionName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Wire up the Svelar test environment in a describe block.
|
|
15
|
+
*
|
|
16
|
+
* Call this at the top of a `describe()` block:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* describe('UserService', () => {
|
|
20
|
+
* useSvelarTest({ refreshDatabase: true });
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Automatically:
|
|
26
|
+
* - Configures an in-memory SQLite database
|
|
27
|
+
* - Resets factory sequences between tests
|
|
28
|
+
* - Optionally runs refreshDatabase() before each test
|
|
29
|
+
*/
|
|
30
|
+
export declare function useSvelarTest(options?: UseSvelarTestOptions): void;
|
|
31
|
+
/**
|
|
32
|
+
* Drop all tables and re-run all migrations from the project's
|
|
33
|
+
* `src/lib/database/migrations/` directory.
|
|
34
|
+
*/
|
|
35
|
+
export declare function refreshDatabase(connectionName?: string): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beeblock/svelar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Laravel-inspired framework on top of SvelteKit 2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -245,6 +245,10 @@
|
|
|
245
245
|
"./stripe": {
|
|
246
246
|
"types": "./dist/stripe/index.d.ts",
|
|
247
247
|
"import": "./dist/stripe/index.js"
|
|
248
|
+
},
|
|
249
|
+
"./testing": {
|
|
250
|
+
"types": "./dist/testing/index.d.ts",
|
|
251
|
+
"import": "./dist/testing/index.js"
|
|
248
252
|
}
|
|
249
253
|
},
|
|
250
254
|
"scripts": {
|
|
@@ -289,7 +293,8 @@
|
|
|
289
293
|
"postgres": "*",
|
|
290
294
|
"pusher-js": ">=8.0.0",
|
|
291
295
|
"stripe": ">=14.0.0",
|
|
292
|
-
"sveltekit-superforms": ">=2.0.0"
|
|
296
|
+
"sveltekit-superforms": ">=2.0.0",
|
|
297
|
+
"vitest": ">=1.0.0"
|
|
293
298
|
},
|
|
294
299
|
"peerDependenciesMeta": {
|
|
295
300
|
"@aws-sdk/client-s3": {
|
|
@@ -330,6 +335,9 @@
|
|
|
330
335
|
},
|
|
331
336
|
"stripe": {
|
|
332
337
|
"optional": true
|
|
338
|
+
},
|
|
339
|
+
"vitest": {
|
|
340
|
+
"optional": true
|
|
333
341
|
}
|
|
334
342
|
},
|
|
335
343
|
"keywords": [
|