-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
## Managing Durable Objects Migrations | ||
|
||
In order to automatically manage migrations inside Durable Objects, just apply run the apply method inside the constructor | ||
|
||
```ts | ||
import { DurableObject } from 'cloudflare:workers' | ||
import { DOQB } from '../src' | ||
import { Env } from './bindings' | ||
|
||
export const migrations: Migration[] = [ | ||
{ | ||
name: '100000000000000_add_logs_table.sql', | ||
sql: ` | ||
create table logs | ||
( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
name TEXT NOT NULL | ||
);`, | ||
}, | ||
] | ||
|
||
export class TestDO extends DurableObject { | ||
constructor(state: DurableObjectState, env: Env) { | ||
super(state, env) | ||
|
||
void this.ctx.blockConcurrencyWhile(async () => { | ||
const qb = new DOQB(this.ctx.storage.sql) | ||
qb.migrations({ migrations }).apply() | ||
}) | ||
} | ||
} | ||
``` | ||
|
||
Having this code inside the constructor will automatically apply new migrations when you update your worker. | ||
|
||
|
||
## Methods | ||
|
||
#### `migrations()` | ||
|
||
```typescript | ||
qb.migrations(options: MigrationOptions): Migrations | ||
``` | ||
- **Parameters:** | ||
|
||
- `options: MigrationOptions` - An object containing migrations and optional table name. | ||
|
||
- `migrations: Array<Migration>` - An array of migration objects to be applied. | ||
|
||
- `tableName?: string` - The name of the table to store migration records, defaults to 'migrations'. | ||
|
||
#### `initialize()` | ||
|
||
```typescript | ||
initialize(): void | ||
``` | ||
- **Description:** | ||
|
||
- Initializes the migration table if it doesn't exist. Creates a table named according to `_tableName` or `migrations` if non is set, with columns for `id`, `name`, and `applied_at`. | ||
|
||
#### `getApplied()` | ||
|
||
```typescript | ||
getApplied(): Array<MigrationEntry> | ||
``` | ||
- **Description:** | ||
|
||
- Fetches all migrations that have been applied from the database. | ||
|
||
- **Returns:** An array of `MigrationEntry` objects representing applied migrations. | ||
|
||
#### `getUnapplied()` | ||
|
||
```typescript | ||
getUnapplied(): Array<Migration> | ||
``` | ||
- **Description:** | ||
|
||
- Compares the list of all migrations with those that have been applied to determine which ones remain unapplied. | ||
|
||
- **Returns:** An array of `Migration` objects that have not yet been applied. | ||
|
||
#### `apply()` | ||
|
||
```typescript | ||
apply(): Array<Migration> | ||
``` | ||
- **Description:** | ||
|
||
- Applies all unapplied migrations by executing their SQL statements and logging the migration to the migration table. | ||
|
||
- **Returns:** An array of `Migration` objects that were applied during this call. | ||
|
||
### Type Definitions | ||
|
||
#### MigrationEntry | ||
|
||
```typescript | ||
type MigrationEntry = { | ||
id: number | ||
name: string | ||
applied_at: Date | ||
} | ||
``` | ||
- **Fields:** | ||
- `id`: The unique identifier for each migration entry. | ||
- `name`: The name of the migration. | ||
- `applied_at`: The timestamp when the migration was applied. | ||
#### Migration | ||
```typescript | ||
type Migration = { | ||
name: string | ||
sql: string | ||
} | ||
``` | ||
- **Fields:** | ||
- `name`: The name of the migration. | ||
- `sql`: The SQL command to execute for this migration. | ||
#### MigrationOptions | ||
```typescript | ||
type MigrationOptions = { | ||
migrations: Array<Migration> | ||
tableName?: string | ||
} | ||
``` | ||
- **Fields:** | ||
- `migrations`: An array of migration objects. | ||
- `tableName`: Optional name for the migrations table. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export type Env = { | ||
DB: D1Database | ||
TEST_DO: DurableObjectNamespace | ||
} | ||
|
||
declare module 'cloudflare:test' { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { DurableObject } from 'cloudflare:workers' | ||
import { DOQB } from '../src' | ||
import { Env } from './bindings' | ||
import { migrations } from './integration/migrations-do.test' | ||
|
||
export class TestDO extends DurableObject { | ||
constructor(state: DurableObjectState, env: Env) { | ||
super(state, env) | ||
|
||
void this.ctx.blockConcurrencyWhile(async () => { | ||
const qb = new DOQB(this.ctx.storage.sql) | ||
|
||
qb.migrations({ migrations }).apply() | ||
}) | ||
} | ||
} | ||
|
||
export default { | ||
async fetch(request: Request, env: Env) { | ||
return new Response('test') | ||
}, | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { env, runInDurableObject } from 'cloudflare:test' | ||
import { describe, expect, it } from 'vitest' | ||
import { D1QB, DOQB, Migration } from '../../src' | ||
|
||
export const migrations: Migration[] = [ | ||
{ | ||
name: '100000000000000_add_logs_table.sql', | ||
sql: ` | ||
create table logs | ||
( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
name TEXT NOT NULL | ||
);`, | ||
}, | ||
] | ||
|
||
describe('Migrations', () => { | ||
it('initialize', async () => { | ||
const id = env.TEST_DO.idFromName('test') | ||
const stub = env.TEST_DO.get(id) | ||
|
||
await runInDurableObject(stub, async (_instance, state) => { | ||
// Initialize is called inside DO constructor | ||
|
||
expect( | ||
Array.from( | ||
state.storage.sql.exec(`SELECT name | ||
FROM sqlite_master | ||
WHERE type = 'table' | ||
AND name not in ('_cf_KV', 'sqlite_sequence', '_cf_METADATA')`) | ||
) | ||
).toEqual([ | ||
{ | ||
name: 'migrations', | ||
}, | ||
{ | ||
name: 'logs', | ||
}, | ||
]) | ||
}) | ||
}) | ||
|
||
it('apply', async () => { | ||
const id = env.TEST_DO.idFromName('test') | ||
const stub = env.TEST_DO.get(id) | ||
|
||
await runInDurableObject(stub, async (_instance, state) => { | ||
// Initialize is called inside DO constructor | ||
|
||
expect( | ||
Array.from( | ||
state.storage.sql.exec(`SELECT name | ||
FROM sqlite_master | ||
WHERE type = 'table' | ||
AND name not in ('_cf_KV', 'sqlite_sequence', '_cf_METADATA')`) | ||
) | ||
).toEqual([ | ||
{ | ||
name: 'migrations', | ||
}, | ||
{ | ||
name: 'logs', | ||
}, | ||
]) | ||
|
||
const qb = new DOQB(state.storage.sql) | ||
const applyResp2 = qb.migrations({ migrations }).apply() | ||
expect(applyResp2.length).toEqual(0) | ||
}) | ||
}) | ||
|
||
it('incremental migrations', async () => { | ||
const id = env.TEST_DO.idFromName('test') | ||
const stub = env.TEST_DO.get(id) | ||
|
||
await runInDurableObject(stub, async (_instance, state) => { | ||
// Initialize is called inside DO constructor | ||
|
||
const updatedMigrations = [ | ||
...migrations, | ||
{ | ||
name: '100000000000001_add_second_table.sql', | ||
sql: ` | ||
create table logs_two | ||
( | ||
id INTEGER PRIMARY KEY AUTOINCREMENT, | ||
name TEXT NOT NULL | ||
);`, | ||
}, | ||
] | ||
|
||
const qb = new DOQB(state.storage.sql) | ||
const applyResp2 = qb.migrations({ migrations: updatedMigrations }).apply() | ||
|
||
expect(applyResp2.length).toEqual(1) | ||
expect(applyResp2[0]?.name).toEqual('100000000000001_add_second_table.sql') | ||
|
||
const applyResp3 = qb.migrations({ migrations: updatedMigrations }).apply() | ||
expect(applyResp3.length).toEqual(0) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name = "test" | ||
main = "index.ts" | ||
compatibility_date = "2024-11-09" | ||
|
||
[[durable_objects.bindings]] | ||
name = "TEST_DO" | ||
class_name = "TestDO" | ||
|
||
[[migrations]] | ||
tag = "v1" | ||
new_sqlite_classes = ["TestDO"] |