From c914cd2e708a65e1011eec2b7f02ad0e18277dfa Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 12:35:55 +0000 Subject: [PATCH 1/3] Store stack config as a _config@1 record instead of getConfig/setConfig Removes getConfig/setConfig from StackAdapter. Stack-level configuration (ownerEntityId, timezone) is now stored as a singleton _config@1 record in the records table and exposed as typed readonly properties on the adapter. - StackAdapter gains ownerEntityId and timezone as required properties - ConfigContent type and SYSTEM_TYPES.CONFIG added to core - Stack.create() reads directly from adapter properties - seedSystemTypes() seeds the _config@1 type definition - SQLiteAdapter stores config as a _config@1 record; runMigrations() handles existing databases with a stack_config table - buildWhereClause excludes the singleton _config record from all queries - APIAdapter reads ownerEntityId/timezone from the discovery response - MemoryAdapter constructor takes { ownerEntityId?, timezone? } options - All tests updated accordingly Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01FiDqP6DsUEgtxTaAUj62iE --- packages/adapter-api/src/index.ts | 35 ++++----- packages/adapter-api/tests/api.test.ts | 52 ++++--------- packages/adapter-sqlite/src/index.ts | 79 ++++++++++++-------- packages/adapter-sqlite/tests/sqlite.test.ts | 41 +++------- packages/core/src/index.ts | 1 + packages/core/src/stack.ts | 16 ++-- packages/core/src/testing.ts | 19 +++-- packages/core/src/types.ts | 17 ++++- packages/core/tests/scoped-stack.test.ts | 2 +- packages/core/tests/stack.test.ts | 17 ++--- 10 files changed, 126 insertions(+), 153 deletions(-) diff --git a/packages/adapter-api/src/index.ts b/packages/adapter-api/src/index.ts index 529b9f3..d64caa1 100644 --- a/packages/adapter-api/src/index.ts +++ b/packages/adapter-api/src/index.ts @@ -171,14 +171,19 @@ const buildQueryParams = (query: StackQuery): URLSearchParams => { export class APIAdapter implements StackAdapter { readonly capabilities: AdapterCapabilities; + readonly ownerEntityId: string; + readonly timezone: string; private constructor( private readonly baseUrl: string, private readonly token: string | undefined, - private readonly config: Map, + ownerEntityId: string, + timezone: string, capabilities: AdapterCapabilities, ) { this.capabilities = capabilities; + this.ownerEntityId = ownerEntityId; + this.timezone = timezone; } /** @@ -208,13 +213,13 @@ export class APIAdapter implements StackAdapter { const discovery = (await res.json()) as DiscoveryResponse; - const config = new Map([ - ['entity_id', discovery.entityId], - ['version', discovery.version], - ]); - if (discovery.timezone) config.set('timezone', discovery.timezone); - - return new APIAdapter(baseUrl, opts.token, config, discovery.capabilities); + return new APIAdapter( + baseUrl, + opts.token, + discovery.entityId, + discovery.timezone ?? 'UTC', + discovery.capabilities, + ); } // ------------------------------------------------------- @@ -292,20 +297,6 @@ export class APIAdapter implements StackAdapter { return res.json() as Promise>; } - // ------------------------------------------------------- - // Config - // ------------------------------------------------------- - - async getConfig(key: string): Promise { - return this.config.get(key) ?? null; - } - - async setConfig(_key: string, _value: string): Promise { - throw new APIAdapterError( - 'setConfig is not supported: server configuration is managed server-side', - ); - } - // ------------------------------------------------------- // Records // ------------------------------------------------------- diff --git a/packages/adapter-api/tests/api.test.ts b/packages/adapter-api/tests/api.test.ts index 45d6e51..b70f286 100644 --- a/packages/adapter-api/tests/api.test.ts +++ b/packages/adapter-api/tests/api.test.ts @@ -109,6 +109,21 @@ describe('open', () => { expect(adapter.capabilities.sortableFields).toEqual(['createdAt', 'updatedAt', 'version']); }); + test('populates ownerEntityId from discovery response', async () => { + const adapter = await openAdapter(); + expect(adapter.ownerEntityId).toBe('entity-owner-123'); + }); + + test('populates timezone from discovery response', async () => { + const adapter = await openAdapter(); + expect(adapter.timezone).toBe('America/New_York'); + }); + + test('defaults timezone to UTC when not in discovery response', async () => { + const adapter = await openAdapter({ ...DISCOVERY, timezone: undefined }); + expect(adapter.timezone).toBe('UTC'); + }); + test('omits Authorization header when no token provided', async () => { mockFetch.mockResolvedValueOnce(jsonResponse(DISCOVERY)); await APIAdapter.open({ url: BASE_URL }); @@ -136,43 +151,6 @@ describe('open', () => { }); }); -// ------------------------------------------------------- -// getConfig -// ------------------------------------------------------- - -describe('getConfig', () => { - test('returns entity_id from discovery', async () => { - const adapter = await openAdapter(); - expect(await adapter.getConfig('entity_id')).toBe('entity-owner-123'); - }); - - test('returns timezone from discovery', async () => { - const adapter = await openAdapter(); - expect(await adapter.getConfig('timezone')).toBe('America/New_York'); - }); - - test('returns version from discovery', async () => { - const adapter = await openAdapter(); - expect(await adapter.getConfig('version')).toBe('1.0'); - }); - - test('returns null for unknown keys', async () => { - const adapter = await openAdapter(); - expect(await adapter.getConfig('nonexistent')).toBeNull(); - }); -}); - -// ------------------------------------------------------- -// setConfig -// ------------------------------------------------------- - -describe('setConfig', () => { - test('throws APIAdapterError — server owns its config', async () => { - const adapter = await openAdapter(); - await expect(adapter.setConfig('timezone', 'UTC')).rejects.toThrow(APIAdapterError); - }); -}); - // ------------------------------------------------------- // createRecord // ------------------------------------------------------- diff --git a/packages/adapter-sqlite/src/index.ts b/packages/adapter-sqlite/src/index.ts index 306370c..2bab6c3 100644 --- a/packages/adapter-sqlite/src/index.ts +++ b/packages/adapter-sqlite/src/index.ts @@ -64,11 +64,6 @@ export type TokenInfo = { // ------------------------------------------------------- const SCHEMA_SQL = ` - CREATE TABLE IF NOT EXISTS stack_config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL - ) STRICT; - CREATE TABLE IF NOT EXISTS records ( id TEXT PRIMARY KEY, type_id TEXT NOT NULL, @@ -299,7 +294,7 @@ const getSortColumn = (field: SortField): string => field === 'createdAt' ? 'created_at' : field === 'updatedAt' ? 'updated_at' : 'version'; const buildWhereClause = (query: StackQuery): { sql: string; params: unknown[] } => { - const conditions: string[] = []; + const conditions: string[] = ["r.id != '_config'"]; const params: unknown[] = []; const f = query.filter ?? {}; @@ -460,6 +455,9 @@ export class SQLiteAdapter implements StackAdapter { sortableFields: ['createdAt', 'updatedAt', 'version'], }; + ownerEntityId!: string; + timezone!: string; + private db!: Database; private readonly attachmentsDir: string; @@ -487,11 +485,14 @@ export class SQLiteAdapter implements StackAdapter { adapter.db.run(SCHEMA_SQL); adapter.runMigrations(); mkdirSync(adapter.attachmentsDir, { recursive: true }); + const now = Date.now(); adapter.db.run( - `INSERT INTO stack_config (key, value) VALUES - ('entity_id', ?), ('timezone', ?), ('version', '1')`, - [opts.entityId, opts.timezone], + `INSERT INTO records (id, type_id, created_at, updated_at, content, version) + VALUES ('_config', '_config@1', ?, ?, ?, 1)`, + [now, now, JSON.stringify({ entityId: opts.entityId, timezone: opts.timezone })], ); + adapter.ownerEntityId = opts.entityId; + adapter.timezone = opts.timezone; adapter.persist(); return adapter; } @@ -514,30 +515,10 @@ export class SQLiteAdapter implements StackAdapter { adapter.db.run(SCHEMA_SQL); // safe — all statements use CREATE IF NOT EXISTS adapter.runMigrations(); mkdirSync(adapter.attachmentsDir, { recursive: true }); + adapter.readConfig(); return adapter; } - // ------------------------------------------------------- - // Config - // ------------------------------------------------------- - - async getConfig(key: string): Promise { - const stmt = this.db.prepare('SELECT value FROM stack_config WHERE key = ?'); - stmt.bind([key]); - if (stmt.step()) { - const row = stmt.getAsObject(); - stmt.free(); - return row.value as string; - } - stmt.free(); - return null; - } - - async setConfig(key: string, value: string): Promise { - this.db.run('INSERT OR REPLACE INTO stack_config (key, value) VALUES (?, ?)', [key, value]); - this.persist(); - } - // ------------------------------------------------------- // Persistence // ------------------------------------------------------- @@ -560,8 +541,42 @@ export class SQLiteAdapter implements StackAdapter { // The attachments table is obsolete — binary files are content-addressed on // disk and the filesystem is the authoritative source of truth. Drop it if // an older database still has it. - const cols = this.execQuery<{ name: string }>('PRAGMA table_info(attachments)'); - if (cols.length) this.db.run('DROP TABLE attachments'); + const attachmentCols = this.execQuery<{ name: string }>('PRAGMA table_info(attachments)'); + if (attachmentCols.length) this.db.run('DROP TABLE attachments'); + + // stack_config is obsolete — config is now a _config@1 record in the records + // table. Migrate the data and drop the table if it still exists. + const configCols = this.execQuery<{ name: string }>('PRAGMA table_info(stack_config)'); + if (configCols.length) { + const configRows = this.execQuery<{ key: string; value: string }>( + 'SELECT key, value FROM stack_config', + ); + const cfg: Record = {}; + for (const row of configRows) cfg[row.key] = row.value; + + const existing = this.execQuery<{ id: string }>( + `SELECT id FROM records WHERE id = '_config'`, + ); + if (!existing.length) { + const now = Date.now(); + this.db.run( + `INSERT INTO records (id, type_id, created_at, updated_at, content, version) + VALUES ('_config', '_config@1', ?, ?, ?, 1)`, + [now, now, JSON.stringify({ entityId: cfg['entity_id'], timezone: cfg['timezone'] ?? 'UTC' })], + ); + } + this.db.run('DROP TABLE stack_config'); + } + } + + private readConfig(): void { + const rows = this.execQuery<{ content: string }>( + `SELECT content FROM records WHERE id = '_config'`, + ); + if (!rows.length) throw new Error('Stack database is missing its config record.'); + const content = JSON.parse(rows[0].content) as { entityId: string; timezone?: string }; + this.ownerEntityId = content.entityId; + this.timezone = content.timezone ?? 'UTC'; } // ------------------------------------------------------- diff --git a/packages/adapter-sqlite/tests/sqlite.test.ts b/packages/adapter-sqlite/tests/sqlite.test.ts index 77cd9d0..e46b4dd 100644 --- a/packages/adapter-sqlite/tests/sqlite.test.ts +++ b/packages/adapter-sqlite/tests/sqlite.test.ts @@ -64,19 +64,14 @@ describe('initialize', () => { expect(existsSync(join(testDir, 'attachments'))).toBe(true); }); - test('stores entity_id in config', async () => { + test('sets ownerEntityId', async () => { const adapter = await initAdapter({ entityId: 'owner-abc' }); - expect(await adapter.getConfig('entity_id')).toBe('owner-abc'); + expect(adapter.ownerEntityId).toBe('owner-abc'); }); - test('stores timezone in config', async () => { + test('sets timezone', async () => { const adapter = await initAdapter({ timezone: 'Europe/London' }); - expect(await adapter.getConfig('timezone')).toBe('Europe/London'); - }); - - test('stores version in config', async () => { - const adapter = await initAdapter(); - expect(await adapter.getConfig('version')).toBe('1'); + expect(adapter.timezone).toBe('Europe/London'); }); test('throws if database already exists', async () => { @@ -89,7 +84,7 @@ describe('open', () => { test('opens an existing database', async () => { await initAdapter(); const adapter = await SQLiteAdapter.open({ path: dbPath }); - expect(await adapter.getConfig('entity_id')).toBe('entity-123'); + expect(adapter.ownerEntityId).toBe('entity-123'); }); test('throws if database does not exist', async () => { @@ -111,28 +106,12 @@ describe('open', () => { }); }); -// ------------------------------------------------------- -// Config -// ------------------------------------------------------- - -describe('config', () => { - test('setConfig stores a value', async () => { - const adapter = await initAdapter(); - await adapter.setConfig('custom_key', 'custom_value'); - expect(await adapter.getConfig('custom_key')).toBe('custom_value'); - }); - - test('setConfig overwrites existing value', async () => { - const adapter = await initAdapter({ timezone: 'UTC' }); - await adapter.setConfig('timezone', 'Asia/Tokyo'); - expect(await adapter.getConfig('timezone')).toBe('Asia/Tokyo'); - }); - - test('getConfig returns null for missing key', async () => { - const adapter = await initAdapter(); - expect(await adapter.getConfig('nonexistent')).toBeNull(); + test('preserves ownerEntityId and timezone across reopen', async () => { + await initAdapter({ entityId: 'owner-abc', timezone: 'Europe/London' }); + const adapter = await SQLiteAdapter.open({ path: dbPath }); + expect(adapter.ownerEntityId).toBe('owner-abc'); + expect(adapter.timezone).toBe('Europe/London'); }); -}); // ------------------------------------------------------- // Types diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4c79fed..934110a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -63,6 +63,7 @@ export type { GroupContent, GrantAction, GrantContent, + ConfigContent, } from './types.js'; export { SYSTEM_TYPES } from './types.js'; diff --git a/packages/core/src/stack.ts b/packages/core/src/stack.ts index e501c84..91037be 100644 --- a/packages/core/src/stack.ts +++ b/packages/core/src/stack.ts @@ -140,20 +140,16 @@ export class Stack implements StackClient { ) {} /** - * Create a Stack instance. Reads ownerEntityId and timezone from the - * adapter's config — the adapter is the single source of truth for - * stack-level configuration. + * Create a Stack instance. Reads ownerEntityId and timezone from the adapter. */ static async create(adapter: StackAdapter): Promise { - const entityId = await adapter.getConfig('entity_id'); - if (!entityId) { + if (!adapter.ownerEntityId) { throw new Error( - 'Stack misconfiguration: adapter has no entity_id. ' + + 'Stack misconfiguration: adapter has no ownerEntityId. ' + 'Initialise the adapter with an entityId before calling Stack.create().', ); } - const timezone = (await adapter.getConfig('timezone')) ?? 'UTC'; - const stack = new Stack(adapter, entityId, timezone); + const stack = new Stack(adapter, adapter.ownerEntityId, adapter.timezone); await stack.seedSystemTypes(); return stack; } @@ -663,6 +659,10 @@ export class Stack implements StackClient { // ------------------------------------------------------- private async seedSystemTypes(): Promise { + await this.defineType(`${SYSTEM_TYPES.CONFIG}@1`, 'Config', { + entityId: { kind: 'string', required: true }, + timezone: { kind: 'string', required: true }, + }); await this.defineType(`${SYSTEM_TYPES.ENTITY}@1`, 'Entity', { name: { kind: 'string', required: true }, handle: { kind: 'string' }, diff --git a/packages/core/src/testing.ts b/packages/core/src/testing.ts index c8c8bb4..a757486 100644 --- a/packages/core/src/testing.ts +++ b/packages/core/src/testing.ts @@ -21,21 +21,20 @@ export class MemoryAdapter implements StackAdapter { sortableFields: ['createdAt', 'updatedAt', 'version'], }; + readonly ownerEntityId: string; + readonly timezone: string; + readonly records = new Map(); readonly order: string[] = []; readonly versions = new Map(); readonly types = new Map(); - readonly config: Map; - - constructor(initialConfig: Record = {}) { - this.config = new Map(Object.entries(initialConfig)); - } - async getConfig(key: string) { - return this.config.get(key) ?? null; - } - async setConfig(key: string, value: string) { - this.config.set(key, value); + constructor({ + ownerEntityId = '', + timezone = 'UTC', + }: { ownerEntityId?: string; timezone?: string } = {}) { + this.ownerEntityId = ownerEntityId; + this.timezone = timezone; } async createRecord(record: StackRecord) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 5584d99..6141b84 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -210,6 +210,14 @@ export type AttachmentContent = { filename?: string; }; +/** Content for _config records — one singleton per stack, created on initialization. */ +export type ConfigContent = { + /** Entity ID of the stack owner. */ + entityId: string; + /** IANA timezone string e.g. "America/New_York". */ + timezone: string; +}; + /** Reserved system type IDs */ export const SYSTEM_TYPES = { ENTITY: '_entity', @@ -219,6 +227,8 @@ export const SYSTEM_TYPES = { GRANT: '_grant', /** Attachment metadata records. See AttachmentContent. */ ATTACHMENT: '_attachment', + /** Stack-level configuration singleton. See ConfigContent. */ + CONFIG: '_config', } as const; // ------------------------------------------------------- @@ -319,9 +329,10 @@ export type StackFeatures = AdapterCapabilities; export interface StackAdapter { readonly capabilities: AdapterCapabilities; - // Config - getConfig(key: string): Promise; - setConfig(key: string, value: string): Promise; + /** Entity ID of the stack owner. Set during adapter initialization. */ + readonly ownerEntityId: string; + /** IANA timezone string for this stack e.g. "America/New_York". */ + readonly timezone: string; // Records createRecord(record: StackRecord): Promise; diff --git a/packages/core/tests/scoped-stack.test.ts b/packages/core/tests/scoped-stack.test.ts index 7cb3395..363e7dc 100644 --- a/packages/core/tests/scoped-stack.test.ts +++ b/packages/core/tests/scoped-stack.test.ts @@ -35,7 +35,7 @@ function makeRecord(overrides: Partial = {}): StackRecord { } beforeEach(async () => { - adapter = new MemoryAdapter({ entity_id: OWNER, timezone: 'UTC' }); + adapter = new MemoryAdapter({ ownerEntityId: OWNER, timezone: 'UTC' }); stack = await Stack.create(adapter); await stack.defineType(NOTE, 'Note', { text: { kind: 'text' } }); }); diff --git a/packages/core/tests/stack.test.ts b/packages/core/tests/stack.test.ts index ab0e164..3e10128 100644 --- a/packages/core/tests/stack.test.ts +++ b/packages/core/tests/stack.test.ts @@ -14,7 +14,7 @@ let adapter: MemoryAdapter; let stack: Stack; beforeEach(async () => { - adapter = new MemoryAdapter({ entity_id: 'owner-123', timezone: 'UTC' }); + adapter = new MemoryAdapter({ ownerEntityId: 'owner-123', timezone: 'UTC' }); stack = await Stack.create(adapter); await stack.defineType(NOTE_V1, 'Note', { @@ -27,25 +27,24 @@ beforeEach(async () => { // ------------------------------------------------------- describe('Stack.create', () => { - test('reads ownerEntityId from adapter config', async () => { + test('reads ownerEntityId from adapter', async () => { expect(stack.ownerEntityId).toBe('owner-123'); }); - test('reads timezone from adapter config', async () => { + test('reads timezone from adapter', async () => { expect(stack.timezone).toBe('UTC'); }); - test('throws if adapter has no entity_id', async () => { + test('throws if adapter has no ownerEntityId', async () => { const emptyAdapter = new MemoryAdapter(); await expect(Stack.create(emptyAdapter)).rejects.toThrow( - 'Stack misconfiguration: adapter has no entity_id', + 'Stack misconfiguration: adapter has no ownerEntityId', ); }); - test('timezone defaults to UTC if not set in config', async () => { - const adapterWithEntity = new MemoryAdapter(); - await adapterWithEntity.setConfig('entity_id', 'entity-without-timezone'); - const s = await Stack.create(adapterWithEntity); + test('timezone defaults to UTC when not specified', async () => { + const adapter = new MemoryAdapter({ ownerEntityId: 'entity-without-timezone' }); + const s = await Stack.create(adapter); expect(s.timezone).toBe('UTC'); }); }); From 7682879392d75816655d064179e79189597e9a87 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 13:23:13 +0000 Subject: [PATCH 2/3] =?UTF-8?q?Remove=20DB=20migration=20code=20=E2=80=94?= =?UTF-8?q?=20no=20production=20deployments=20to=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01FiDqP6DsUEgtxTaAUj62iE --- packages/adapter-sqlite/src/index.ts | 48 ++-------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/packages/adapter-sqlite/src/index.ts b/packages/adapter-sqlite/src/index.ts index 2bab6c3..a750bc6 100644 --- a/packages/adapter-sqlite/src/index.ts +++ b/packages/adapter-sqlite/src/index.ts @@ -11,8 +11,8 @@ * File IDs are SHA-256 hashes of the content; the filesystem * is the authoritative store — no separate DB table is needed. * - * Stack config (timezone, entity_id, etc.) is stored in a - * `stack_config` key/value table. + * Stack config (ownerEntityId, timezone) is stored as a singleton + * _config@1 record with id='_config' in the records table. */ import initSqlJs from 'sql.js'; @@ -483,7 +483,6 @@ export class SQLiteAdapter implements StackAdapter { const adapter = new SQLiteAdapter(SQL, opts.path); adapter.db = new SQL.Database(); adapter.db.run(SCHEMA_SQL); - adapter.runMigrations(); mkdirSync(adapter.attachmentsDir, { recursive: true }); const now = Date.now(); adapter.db.run( @@ -512,8 +511,7 @@ export class SQLiteAdapter implements StackAdapter { const adapter = new SQLiteAdapter(SQL, opts.path); const fileBuffer = readFileSync(opts.path); adapter.db = new SQL.Database(fileBuffer); - adapter.db.run(SCHEMA_SQL); // safe — all statements use CREATE IF NOT EXISTS - adapter.runMigrations(); + adapter.db.run(SCHEMA_SQL); mkdirSync(adapter.attachmentsDir, { recursive: true }); adapter.readConfig(); return adapter; @@ -529,46 +527,6 @@ export class SQLiteAdapter implements StackAdapter { writeFileSync(this.path, Buffer.from(data)); } - // ------------------------------------------------------- - // Schema migrations - // ------------------------------------------------------- - - /** - * Runs after SCHEMA_SQL to handle databases created before breaking schema - * changes. Safe to call on both fresh and existing databases. - */ - private runMigrations(): void { - // The attachments table is obsolete — binary files are content-addressed on - // disk and the filesystem is the authoritative source of truth. Drop it if - // an older database still has it. - const attachmentCols = this.execQuery<{ name: string }>('PRAGMA table_info(attachments)'); - if (attachmentCols.length) this.db.run('DROP TABLE attachments'); - - // stack_config is obsolete — config is now a _config@1 record in the records - // table. Migrate the data and drop the table if it still exists. - const configCols = this.execQuery<{ name: string }>('PRAGMA table_info(stack_config)'); - if (configCols.length) { - const configRows = this.execQuery<{ key: string; value: string }>( - 'SELECT key, value FROM stack_config', - ); - const cfg: Record = {}; - for (const row of configRows) cfg[row.key] = row.value; - - const existing = this.execQuery<{ id: string }>( - `SELECT id FROM records WHERE id = '_config'`, - ); - if (!existing.length) { - const now = Date.now(); - this.db.run( - `INSERT INTO records (id, type_id, created_at, updated_at, content, version) - VALUES ('_config', '_config@1', ?, ?, ?, 1)`, - [now, now, JSON.stringify({ entityId: cfg['entity_id'], timezone: cfg['timezone'] ?? 'UTC' })], - ); - } - this.db.run('DROP TABLE stack_config'); - } - } - private readConfig(): void { const rows = this.execQuery<{ content: string }>( `SELECT content FROM records WHERE id = '_config'`, From 419e973cd1623b2f1dbe46d6334cc1f4e75913e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 13:32:13 +0000 Subject: [PATCH 3/3] Delegate Stack.ownerEntityId and timezone to the adapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No reason to store them twice — the adapter is the source of truth. Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_01FiDqP6DsUEgtxTaAUj62iE --- packages/core/src/stack.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/src/stack.ts b/packages/core/src/stack.ts index 91037be..bfa906b 100644 --- a/packages/core/src/stack.ts +++ b/packages/core/src/stack.ts @@ -133,11 +133,7 @@ export interface StackClient { export class Stack implements StackClient { private readonly migrations = new Map(); - private constructor( - private readonly adapter: StackAdapter, - readonly ownerEntityId: string, - readonly timezone: string, - ) {} + private constructor(private readonly adapter: StackAdapter) {} /** * Create a Stack instance. Reads ownerEntityId and timezone from the adapter. @@ -149,11 +145,19 @@ export class Stack implements StackClient { 'Initialise the adapter with an entityId before calling Stack.create().', ); } - const stack = new Stack(adapter, adapter.ownerEntityId, adapter.timezone); + const stack = new Stack(adapter); await stack.seedSystemTypes(); return stack; } + get ownerEntityId(): string { + return this.adapter.ownerEntityId; + } + + get timezone(): string { + return this.adapter.timezone; + } + get features(): StackFeatures { return this.adapter.capabilities; }