@datacules/agent-identity-store-azure 0.11.0 → 0.11.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.
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ // Mock Azure SDK modules — constructors return objects with vi.fn() methods.
5
+ // vi.mock() calls are hoisted before imports by Vitest.
6
+ vitest_1.vi.mock('@azure/identity', () => ({
7
+ DefaultAzureCredential: vitest_1.vi.fn(() => ({})),
8
+ }));
9
+ vitest_1.vi.mock('@azure/keyvault-secrets', () => ({
10
+ SecretClient: vitest_1.vi.fn(() => ({
11
+ getSecret: vitest_1.vi.fn(),
12
+ listPropertiesOfSecrets: vitest_1.vi.fn(),
13
+ })),
14
+ }));
15
+ vitest_1.vi.mock('@azure/data-tables', () => ({
16
+ TableClient: vitest_1.vi.fn(() => ({
17
+ getEntity: vitest_1.vi.fn(),
18
+ upsertEntity: vitest_1.vi.fn(),
19
+ deleteEntity: vitest_1.vi.fn(),
20
+ })),
21
+ odata: vitest_1.vi.fn((s) => s),
22
+ }));
23
+ const index_js_1 = require("./index.js");
24
+ const makeCred = (overrides = {}) => ({
25
+ id: 'cred-openai',
26
+ kind: 'fixed',
27
+ name: 'OpenAI Key',
28
+ scope: 'global',
29
+ status: 'active',
30
+ provider: 'openai',
31
+ ref: 'openai-prod-slot',
32
+ ...overrides,
33
+ });
34
+ // Minimal secret response shape returned by SecretClient.getSecret()
35
+ const makeSecretResponse = (cred) => ({
36
+ value: JSON.stringify(cred),
37
+ properties: { contentType: cred.status },
38
+ });
39
+ (0, vitest_1.describe)('AzureKeyVaultCredentialStore', () => {
40
+ let store;
41
+ let secretsMock;
42
+ let tableMock;
43
+ (0, vitest_1.beforeEach)(() => {
44
+ vitest_1.vi.clearAllMocks();
45
+ store = new index_js_1.AzureKeyVaultCredentialStore({
46
+ keyVaultUrl: 'https://test.vault.azure.net',
47
+ tablesEndpoint: 'https://test.table.core.windows.net',
48
+ });
49
+ // Access mock clients injected by the mocked SDK constructors
50
+ secretsMock = store.secrets;
51
+ tableMock = store.table;
52
+ });
53
+ // ─── constructor ────────────────────────────────────────────────────────────
54
+ (0, vitest_1.describe)('constructor', () => {
55
+ (0, vitest_1.it)('throws when keyVaultUrl is missing from both options and environment', () => {
56
+ delete process.env['AZURE_KEYVAULT_URL'];
57
+ (0, vitest_1.expect)(() => new index_js_1.AzureKeyVaultCredentialStore({
58
+ tablesEndpoint: 'https://test.table.core.windows.net',
59
+ })).toThrow('keyVaultUrl is required');
60
+ });
61
+ (0, vitest_1.it)('throws when tablesEndpoint is missing from both options and environment', () => {
62
+ delete process.env['AZURE_TABLES_ENDPOINT'];
63
+ (0, vitest_1.expect)(() => new index_js_1.AzureKeyVaultCredentialStore({
64
+ keyVaultUrl: 'https://test.vault.azure.net',
65
+ })).toThrow('tablesEndpoint is required');
66
+ });
67
+ });
68
+ // ─── findByRef() ────────────────────────────────────────────────────────────
69
+ (0, vitest_1.describe)('findByRef()', () => {
70
+ (0, vitest_1.it)('returns active credential when Key Vault returns a secret with contentType=active', async () => {
71
+ const cred = makeCred();
72
+ secretsMock.getSecret.mockResolvedValue(makeSecretResponse(cred));
73
+ (0, vitest_1.expect)(await store.findByRef('openai-prod-slot')).toEqual(cred);
74
+ });
75
+ (0, vitest_1.it)('returns null when contentType is not active (does not parse the value)', async () => {
76
+ const cred = makeCred({ status: 'pending' });
77
+ secretsMock.getSecret.mockResolvedValue({
78
+ value: JSON.stringify(cred),
79
+ properties: { contentType: 'pending' },
80
+ });
81
+ (0, vitest_1.expect)(await store.findByRef('openai-prod-slot')).toBeNull();
82
+ });
83
+ (0, vitest_1.it)('returns null when secret value is undefined', async () => {
84
+ secretsMock.getSecret.mockResolvedValue({
85
+ value: undefined,
86
+ properties: { contentType: 'active' },
87
+ });
88
+ (0, vitest_1.expect)(await store.findByRef('openai-prod-slot')).toBeNull();
89
+ });
90
+ (0, vitest_1.it)('returns null without throwing when getSecret throws', async () => {
91
+ secretsMock.getSecret.mockRejectedValue(new Error('SecretNotFound'));
92
+ (0, vitest_1.expect)(await store.findByRef('missing-ref')).toBeNull();
93
+ });
94
+ });
95
+ // ─── listActive() ───────────────────────────────────────────────────────────
96
+ (0, vitest_1.describe)('listActive()', () => {
97
+ (0, vitest_1.it)('returns active credentials iterated from listPropertiesOfSecrets and fetched via getSecret', async () => {
98
+ const cred = makeCred();
99
+ secretsMock.listPropertiesOfSecrets.mockReturnValue((async function* () {
100
+ yield { contentType: 'active', enabled: true, name: 'openai-prod-slot' };
101
+ })());
102
+ secretsMock.getSecret.mockResolvedValue(makeSecretResponse(cred));
103
+ const results = await store.listActive();
104
+ (0, vitest_1.expect)(results).toHaveLength(1);
105
+ (0, vitest_1.expect)(results[0]).toEqual(cred);
106
+ });
107
+ (0, vitest_1.it)('skips secrets with contentType !== active without calling getSecret', async () => {
108
+ secretsMock.listPropertiesOfSecrets.mockReturnValue((async function* () {
109
+ yield { contentType: 'pending', enabled: true, name: 'pending-slot' };
110
+ })());
111
+ (0, vitest_1.expect)(await store.listActive()).toHaveLength(0);
112
+ (0, vitest_1.expect)(secretsMock.getSecret).not.toHaveBeenCalled();
113
+ });
114
+ (0, vitest_1.it)('skips secrets with enabled=false', async () => {
115
+ secretsMock.listPropertiesOfSecrets.mockReturnValue((async function* () {
116
+ yield { contentType: 'active', enabled: false, name: 'disabled-slot' };
117
+ })());
118
+ (0, vitest_1.expect)(await store.listActive()).toHaveLength(0);
119
+ });
120
+ (0, vitest_1.it)('returns empty array when listPropertiesOfSecrets throws (Key Vault unreachable)', async () => {
121
+ secretsMock.listPropertiesOfSecrets.mockReturnValue(
122
+ // eslint-disable-next-line require-yield
123
+ (async function* () {
124
+ throw new Error('KeyVaultUnavailable');
125
+ })());
126
+ (0, vitest_1.expect)(await store.listActive()).toEqual([]);
127
+ });
128
+ });
129
+ // ─── listByKind() ───────────────────────────────────────────────────────────
130
+ (0, vitest_1.describe)('listByKind()', () => {
131
+ (0, vitest_1.it)('returns only credentials matching the requested kind', async () => {
132
+ const fixed = makeCred({ kind: 'fixed', id: 'cred-fixed', ref: 'fixed-slot' });
133
+ const delegated = makeCred({ kind: 'user-delegated', id: 'cred-user', ref: 'user-slot' });
134
+ secretsMock.listPropertiesOfSecrets.mockReturnValue((async function* () {
135
+ yield { contentType: 'active', enabled: true, name: 'fixed-slot' };
136
+ yield { contentType: 'active', enabled: true, name: 'user-slot' };
137
+ })());
138
+ secretsMock.getSecret
139
+ .mockResolvedValueOnce(makeSecretResponse(fixed))
140
+ .mockResolvedValueOnce(makeSecretResponse(delegated));
141
+ const result = await store.listByKind('fixed');
142
+ (0, vitest_1.expect)(result).toHaveLength(1);
143
+ (0, vitest_1.expect)(result[0].kind).toBe('fixed');
144
+ });
145
+ });
146
+ // ─── reserve() ──────────────────────────────────────────────────────────────
147
+ (0, vitest_1.describe)('reserve()', () => {
148
+ (0, vitest_1.it)('returns true when no existing lock is found (getEntity throws) and upsert succeeds', async () => {
149
+ tableMock.getEntity.mockRejectedValue(new Error('EntityNotFound'));
150
+ tableMock.upsertEntity.mockResolvedValue({});
151
+ (0, vitest_1.expect)(await store.reserve('cred-ref', 'mig-1', 300)).toBe(true);
152
+ });
153
+ (0, vitest_1.it)('returns false when a different migration holds an unexpired lock', async () => {
154
+ const futureExpiry = Math.floor(Date.now() / 1000) + 600;
155
+ tableMock.getEntity.mockResolvedValue({
156
+ migrationId: 'other-migration',
157
+ expiresAt: futureExpiry,
158
+ });
159
+ (0, vitest_1.expect)(await store.reserve('cred-ref', 'mig-2', 300)).toBe(false);
160
+ });
161
+ (0, vitest_1.it)('returns true when the same migration re-acquires its own lock', async () => {
162
+ const futureExpiry = Math.floor(Date.now() / 1000) + 600;
163
+ tableMock.getEntity.mockResolvedValue({
164
+ migrationId: 'mig-1',
165
+ expiresAt: futureExpiry,
166
+ });
167
+ tableMock.upsertEntity.mockResolvedValue({});
168
+ (0, vitest_1.expect)(await store.reserve('cred-ref', 'mig-1', 300)).toBe(true);
169
+ });
170
+ });
171
+ // ─── release() ──────────────────────────────────────────────────────────────
172
+ (0, vitest_1.describe)('release()', () => {
173
+ (0, vitest_1.it)('deletes the entity when migrationId matches the stored lock owner', async () => {
174
+ tableMock.getEntity.mockResolvedValue({
175
+ migrationId: 'mig-1',
176
+ expiresAt: Math.floor(Date.now() / 1000) + 600,
177
+ });
178
+ tableMock.deleteEntity.mockResolvedValue({});
179
+ await store.release('cred-ref', 'mig-1');
180
+ (0, vitest_1.expect)(tableMock.deleteEntity).toHaveBeenCalledWith('lock', 'cred-ref');
181
+ });
182
+ (0, vitest_1.it)('does not call deleteEntity when migrationId does not match the stored lock', async () => {
183
+ tableMock.getEntity.mockResolvedValue({
184
+ migrationId: 'other-mig',
185
+ expiresAt: Math.floor(Date.now() / 1000) + 600,
186
+ });
187
+ await store.release('cred-ref', 'mig-1');
188
+ (0, vitest_1.expect)(tableMock.deleteEntity).not.toHaveBeenCalled();
189
+ });
190
+ (0, vitest_1.it)('resolves without throwing when getEntity throws (lock already released)', async () => {
191
+ tableMock.getEntity.mockRejectedValue(new Error('EntityNotFound'));
192
+ await (0, vitest_1.expect)(store.release('cred-ref', 'mig-1')).resolves.toBeUndefined();
193
+ });
194
+ });
195
+ });
196
+ //# sourceMappingURL=azure.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure.test.js","sourceRoot":"","sources":["../../src/azure.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AzureKeyVaultCredentialStore = void 0;
4
+ var AzureKeyVaultCredentialStore_1 = require("./AzureKeyVaultCredentialStore");
5
+ Object.defineProperty(exports, "AzureKeyVaultCredentialStore", { enumerable: true, get: function () { return AzureKeyVaultCredentialStore_1.AzureKeyVaultCredentialStore; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,+EAA8E;AAArE,4IAAA,4BAA4B,OAAA"}
@@ -0,0 +1,31 @@
1
+ import type { Credential, CredentialKind, CredentialStore } from '@datacules/agent-identity';
2
+ export interface AzureKeyVaultCredentialStoreOptions {
3
+ /**
4
+ * Full URL of the Azure Key Vault, e.g. https://my-vault.vault.azure.net
5
+ * Falls back to AZURE_KEYVAULT_URL environment variable.
6
+ */
7
+ keyVaultUrl?: string;
8
+ /**
9
+ * Full URL of the Azure Table Storage endpoint,
10
+ * e.g. https://myaccount.table.core.windows.net
11
+ * Falls back to AZURE_TABLES_ENDPOINT environment variable.
12
+ */
13
+ tablesEndpoint?: string;
14
+ /**
15
+ * Name of the Table Storage table used for migration locks.
16
+ * Default: 'agentidentitylocks'
17
+ * Note: Azure Table names may not contain hyphens.
18
+ */
19
+ locksTable?: string;
20
+ }
21
+ export declare class AzureKeyVaultCredentialStore implements CredentialStore {
22
+ private readonly secrets;
23
+ private readonly table;
24
+ constructor(options?: AzureKeyVaultCredentialStoreOptions);
25
+ findByRef(ref: string): Promise<Credential | null>;
26
+ listActive(): Promise<Credential[]>;
27
+ listByKind(kind: CredentialKind): Promise<Credential[]>;
28
+ reserve(ref: string, migrationId: string, ttlSeconds: number): Promise<boolean>;
29
+ release(ref: string, migrationId: string): Promise<void>;
30
+ }
31
+ //# sourceMappingURL=AzureKeyVaultCredentialStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AzureKeyVaultCredentialStore.d.ts","sourceRoot":"","sources":["../../src/AzureKeyVaultCredentialStore.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE7F,MAAM,WAAW,mCAAmC;IAClD;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD,qBAAa,4BAA6B,YAAW,eAAe;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;gBAExB,OAAO,GAAE,mCAAwC;IA4BvD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAalD,UAAU,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAwBnC,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAOvD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkC/E,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAU/D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=azure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure.test.d.ts","sourceRoot":"","sources":["../../src/azure.test.ts"],"names":[],"mappings":""}
@@ -1,2 +1,3 @@
1
1
  export { AzureKeyVaultCredentialStore } from './AzureKeyVaultCredentialStore';
2
2
  export type { AzureKeyVaultCredentialStoreOptions } from './AzureKeyVaultCredentialStore';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,YAAY,EAAE,mCAAmC,EAAE,MAAM,gCAAgC,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,23 @@
1
1
  {
2
2
  "name": "@datacules/agent-identity-store-azure",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "private": false,
5
5
  "description": "Azure Key Vault + Table Storage credential store for @datacules/agent-identity",
6
+ "author": "Datacules LLC",
7
+ "license": "SEE LICENSE IN LICENSE",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/hvrcharon1/agent-identity.git",
11
+ "directory": "packages/stores/azure"
12
+ },
13
+ "keywords": [
14
+ "agent-identity",
15
+ "azure",
16
+ "key-vault",
17
+ "credential-store",
18
+ "ai-agents",
19
+ "datacules"
20
+ ],
6
21
  "main": "./dist/cjs/index.js",
7
22
  "module": "./dist/esm/index.js",
8
23
  "types": "./dist/types/index.d.ts",
@@ -23,7 +38,7 @@
23
38
  "@azure/identity": "^4.3.0"
24
39
  },
25
40
  "peerDependencies": {
26
- "@datacules/agent-identity": "^0.6.0"
41
+ "@datacules/agent-identity": "^0.11.1"
27
42
  },
28
43
  "devDependencies": {
29
44
  "@datacules/agent-identity": "*",
@@ -31,6 +46,7 @@
31
46
  },
32
47
  "files": [
33
48
  "dist",
34
- "src"
49
+ "README.md",
50
+ "LICENSE"
35
51
  ]
36
52
  }
@@ -1,184 +0,0 @@
1
- /**
2
- * Azure Key Vault + Azure Table Storage CredentialStore implementation.
3
- *
4
- * Key Vault holds each credential as a secret whose name is the credential ref.
5
- * The secret value is the JSON-serialised Credential object.
6
- * The secret's "content-type" tag carries the status (active | pending | revoked)
7
- * so listActive() can skip inactive secrets without fetching their values.
8
- *
9
- * Table Storage holds migration reservation locks.
10
- * Table name: agent-identity-locks
11
- * Partition key: "lock" (constant — all locks in one partition for simplicity)
12
- * Row key: the credential ref being locked
13
- * Columns: migrationId (string), expiresAt (number — Unix epoch seconds)
14
- *
15
- * Azure setup:
16
- * 1. Create an Azure Key Vault and store each Credential as a JSON secret.
17
- * Set the secret's ContentType to "active", "pending", or "revoked".
18
- * 2. Create a Storage Account and a Table named "agentidentitylocks".
19
- * (Table names may not contain hyphens — use "agentidentitylocks".)
20
- * 3. Grant the running identity:
21
- * Key Vault Secrets User (read secrets)
22
- * Key Vault Secrets Officer (only needed for write operations)
23
- * Storage Table Data Contributor (read + write locks table)
24
- * 4. Set AZURE_KEYVAULT_URL and AZURE_TABLES_ENDPOINT environment variables,
25
- * or pass them as constructor options.
26
- * DefaultAzureCredential resolves auth automatically from:
27
- * - Managed Identity (production)
28
- * - AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_CLIENT_SECRET env vars
29
- * - Azure CLI (local development)
30
- * - Workload Identity (AKS)
31
- */
32
- import { SecretClient } from '@azure/keyvault-secrets';
33
- import { TableClient, odata } from '@azure/data-tables';
34
- import { DefaultAzureCredential } from '@azure/identity';
35
- import type { Credential, CredentialKind, CredentialStore } from '@datacules/agent-identity';
36
-
37
- export interface AzureKeyVaultCredentialStoreOptions {
38
- /**
39
- * Full URL of the Azure Key Vault, e.g. https://my-vault.vault.azure.net
40
- * Falls back to AZURE_KEYVAULT_URL environment variable.
41
- */
42
- keyVaultUrl?: string;
43
- /**
44
- * Full URL of the Azure Table Storage endpoint,
45
- * e.g. https://myaccount.table.core.windows.net
46
- * Falls back to AZURE_TABLES_ENDPOINT environment variable.
47
- */
48
- tablesEndpoint?: string;
49
- /**
50
- * Name of the Table Storage table used for migration locks.
51
- * Default: 'agentidentitylocks'
52
- * Note: Azure Table names may not contain hyphens.
53
- */
54
- locksTable?: string;
55
- }
56
-
57
- interface LockEntity {
58
- partitionKey: string;
59
- rowKey: string;
60
- migrationId: string;
61
- expiresAt: number;
62
- }
63
-
64
- export class AzureKeyVaultCredentialStore implements CredentialStore {
65
- private readonly secrets: SecretClient;
66
- private readonly table: TableClient;
67
-
68
- constructor(options: AzureKeyVaultCredentialStoreOptions = {}) {
69
- const vaultUrl =
70
- options.keyVaultUrl ?? process.env['AZURE_KEYVAULT_URL'] ?? '';
71
- if (!vaultUrl) {
72
- throw new Error(
73
- 'AzureKeyVaultCredentialStore: keyVaultUrl is required. ' +
74
- 'Pass it as an option or set AZURE_KEYVAULT_URL.'
75
- );
76
- }
77
-
78
- const tablesEndpoint =
79
- options.tablesEndpoint ?? process.env['AZURE_TABLES_ENDPOINT'] ?? '';
80
- if (!tablesEndpoint) {
81
- throw new Error(
82
- 'AzureKeyVaultCredentialStore: tablesEndpoint is required. ' +
83
- 'Pass it as an option or set AZURE_TABLES_ENDPOINT.'
84
- );
85
- }
86
-
87
- const locksTable = options.locksTable ?? 'agentidentitylocks';
88
- const credential = new DefaultAzureCredential();
89
-
90
- this.secrets = new SecretClient(vaultUrl, credential);
91
- this.table = new TableClient(tablesEndpoint, locksTable, credential);
92
- }
93
-
94
- // ─── CredentialStore: reads ──────────────────────────────────────────────
95
-
96
- async findByRef(ref: string): Promise<Credential | null> {
97
- try {
98
- const secret = await this.secrets.getSecret(ref);
99
- if (!secret.value) return null;
100
- // contentType carries status; skip non-active secrets without parsing JSON
101
- if (secret.properties.contentType !== 'active') return null;
102
- const cred: Credential = JSON.parse(secret.value);
103
- return cred.status === 'active' ? cred : null;
104
- } catch {
105
- return null;
106
- }
107
- }
108
-
109
- async listActive(): Promise<Credential[]> {
110
- const results: Credential[] = [];
111
- try {
112
- for await (const secretProperties of this.secrets.listPropertiesOfSecrets()) {
113
- // Only fetch value for secrets tagged as active
114
- if (secretProperties.contentType !== 'active') continue;
115
- if (!secretProperties.enabled) continue;
116
- const name = secretProperties.name;
117
- if (!name) continue;
118
- try {
119
- const secret = await this.secrets.getSecret(name);
120
- if (!secret.value) continue;
121
- const cred: Credential = JSON.parse(secret.value);
122
- if (cred.status === 'active') results.push(cred);
123
- } catch {
124
- // malformed secret — skip
125
- }
126
- }
127
- } catch {
128
- // Key Vault unreachable — return empty rather than throw
129
- }
130
- return results;
131
- }
132
-
133
- async listByKind(kind: CredentialKind): Promise<Credential[]> {
134
- const all = await this.listActive();
135
- return all.filter((c) => c.kind === kind);
136
- }
137
-
138
- // ─── CredentialStore: migration locks ────────────────────────────────────
139
-
140
- async reserve(ref: string, migrationId: string, ttlSeconds: number): Promise<boolean> {
141
- const expiresAt = Math.floor(Date.now() / 1000) + ttlSeconds;
142
- const nowSeconds = Math.floor(Date.now() / 1000);
143
-
144
- // Check for an existing unexpired lock held by a different migration
145
- try {
146
- const existing = await this.table.getEntity<LockEntity>('lock', ref);
147
- if (
148
- existing.migrationId !== migrationId &&
149
- existing.expiresAt > nowSeconds
150
- ) {
151
- return false; // locked by another migration and not yet expired
152
- }
153
- } catch {
154
- // Entity does not exist — proceed to create
155
- }
156
-
157
- // Upsert the lock (merge semantics — overwrites existing row if present)
158
- try {
159
- await this.table.upsertEntity<LockEntity>(
160
- {
161
- partitionKey: 'lock',
162
- rowKey: ref,
163
- migrationId,
164
- expiresAt,
165
- },
166
- 'Replace'
167
- );
168
- return true;
169
- } catch {
170
- return false;
171
- }
172
- }
173
-
174
- async release(ref: string, migrationId: string): Promise<void> {
175
- try {
176
- const existing = await this.table.getEntity<LockEntity>('lock', ref);
177
- // Only delete if this migration owns the lock
178
- if (existing.migrationId !== migrationId) return;
179
- await this.table.deleteEntity('lock', ref);
180
- } catch {
181
- // Already released or never held — idempotent
182
- }
183
- }
184
- }