@hot-updater/azure 1.1.4 → 1.2.0-rc.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.
@@ -261,7 +261,15 @@ const emulatorAborted = (error) => statusOf(error) === 500 && /PostgresError\(Sq
261
261
  * replaces the row it read.
262
262
  */
263
263
  const createCosmosStore = ({ client, databaseId, containerId, engine, nativePageSize }) => {
264
- const container = client.database(databaseId).container(containerId);
264
+ let opened;
265
+ const open = () => opened ??= (() => {
266
+ const account = typeof client === "function" ? client() : client;
267
+ return {
268
+ client: account,
269
+ container: account.database(databaseId).container(containerId)
270
+ };
271
+ })();
272
+ const container = () => open().container;
265
273
  /**
266
274
  * The state of the items `ids` names, by id, with the rows of `rowIds`;
267
275
  * a missing item has none.
@@ -269,7 +277,7 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
269
277
  const readStates = async (ids, rowIds) => {
270
278
  const states = /* @__PURE__ */ new Map();
271
279
  for (const [group, projection] of [[ids.filter((id) => !rowIds.has(id)), ""], [ids.filter((id) => rowIds.has(id)), ", c.row"]]) for (let at = 0; at < group.length; at += GET_BATCH) {
272
- const { resources } = await container.items.query(`SELECT c.id, c._etag AS etag, c.row._v AS v, c.expireAt${projection} FROM c WHERE c.id IN (${idList(group.slice(at, at + GET_BATCH))})`, { partitionKey: COSMOS_PARTITION }).fetchAll().catch(missingContainer);
280
+ const { resources } = await container().items.query(`SELECT c.id, c._etag AS etag, c.row._v AS v, c.expireAt${projection} FROM c WHERE c.id IN (${idList(group.slice(at, at + GET_BATCH))})`, { partitionKey: COSMOS_PARTITION }).fetchAll().catch(missingContainer);
273
281
  for (const { id, etag, v, expireAt, row } of resources) states.set(id, {
274
282
  etag,
275
283
  v,
@@ -407,7 +415,7 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
407
415
  const found = /* @__PURE__ */ new Map();
408
416
  const ids = [...new Set(keys.map(cosmosItemId))];
409
417
  for (let at = 0; at < ids.length; at += GET_BATCH) {
410
- const { resources } = await container.items.query(`SELECT c.id, c.row FROM c WHERE c.id IN (${idList(ids.slice(at, at + GET_BATCH))})`, { partitionKey: COSMOS_PARTITION }).fetchAll().catch(missingContainer);
418
+ const { resources } = await container().items.query(`SELECT c.id, c.row FROM c WHERE c.id IN (${idList(ids.slice(at, at + GET_BATCH))})`, { partitionKey: COSMOS_PARTITION }).fetchAll().catch(missingContainer);
411
419
  for (const resource of resources) found.set(resource.id, toRow(resource.row));
412
420
  }
413
421
  return keys.map((key) => found.get(cosmosItemId(key)) ?? null);
@@ -443,7 +451,7 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
443
451
  value: after
444
452
  });
445
453
  }
446
- const { resources } = await container.items.query({
454
+ const { resources } = await container().items.query({
447
455
  query: `SELECT TOP @size c.sk, c.row FROM c WHERE ${filters.join(" AND ")} ORDER BY c.pk ${order === "asc" ? "ASC" : "DESC"}, c.sk ${order === "asc" ? "ASC" : "DESC"}`,
448
456
  parameters
449
457
  }, {
@@ -493,7 +501,7 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
493
501
  if (planned.length > BATCH_OPERATIONS) throw new Error(`A Cosmos DB batch takes ${BATCH_OPERATIONS} operations; this write needs ${planned.length}.`);
494
502
  let results;
495
503
  try {
496
- const response = await container.items.batch(planned.map(({ operation }) => operation), COSMOS_PARTITION);
504
+ const response = await container().items.batch(planned.map(({ operation }) => operation), COSMOS_PARTITION);
497
505
  if (response.result === void 0) throw Object.assign(/* @__PURE__ */ new Error(`Cosmos DB refused the batch with status ${response.code}`), { code: response.code });
498
506
  results = response.result;
499
507
  } catch (error) {
@@ -527,7 +535,7 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
527
535
  async deleteConsumed(keys) {
528
536
  let remaining = [...new Set(keys.map(cosmosItemId))];
529
537
  for (let attempt = 0; remaining.length > 0; attempt += 1) {
530
- const failed = (await container.items.executeBulkOperations(remaining.map((id) => ({
538
+ const failed = (await container().items.executeBulkOperations(remaining.map((id) => ({
531
539
  operationType: "Delete",
532
540
  id,
533
541
  partitionKey: COSMOS_PARTITION
@@ -548,8 +556,8 @@ const createCosmosStore = ({ client, databaseId, containerId, engine, nativePage
548
556
  migrations: {
549
557
  /** Creates the database and the container when they are missing. */
550
558
  async apply() {
551
- if (engine !== void 0) await assertCosmosContainerReusable(container, engine);
552
- const { database } = await client.databases.createIfNotExists({ id: databaseId });
559
+ if (engine !== void 0) await assertCosmosContainerReusable(container(), engine);
560
+ const { database } = await open().client.databases.createIfNotExists({ id: databaseId });
553
561
  const { resource } = await database.containers.createIfNotExists({
554
562
  id: containerId,
555
563
  partitionKey: { paths: [COSMOS_PARTITION_KEY_PATH] },
@@ -585,15 +593,15 @@ async apply() {
585
593
  const adapterOf = (config) => {
586
594
  const { databaseId = AZURE_COSMOS_V1_DATABASE, containerId = AZURE_COSMOS_V1_CONTAINER, aggregateBatching: _, ...connection } = config;
587
595
  if (connection.consistencyLevel !== void 0 && connection.consistencyLevel !== "Strong") throw new Error(`cosmosDatabase reads with strong consistency; consistencyLevel "${String(connection.consistencyLevel)}" is weaker.`);
588
- const client = new CosmosClient(connection);
596
+ let client;
589
597
  const store = createCosmosStore({
590
- client,
598
+ client: () => client ??= new CosmosClient(connection),
591
599
  databaseId,
592
600
  containerId,
593
601
  engine: currentCosmosEngine()
594
602
  });
595
603
  return {
596
- client,
604
+ dispose: () => client?.dispose(),
597
605
  adapter: createKvAdapter({ store })
598
606
  };
599
607
  };
@@ -605,8 +613,23 @@ const adapterOf = (config) => {
605
613
  * it with the managed server's plugins.
606
614
  */
607
615
  const migrateCosmosDatabase = (config, plugins = []) => {
608
- const { client, adapter } = adapterOf(config);
609
- return migrateCoreSchema(adapter, "cosmosDatabase", plugins).finally(() => client.dispose());
616
+ const { dispose, adapter } = adapterOf(config);
617
+ return migrateCoreSchema(adapter, "cosmosDatabase", plugins).finally(dispose);
618
+ };
619
+ /**
620
+ * The resource this database reaches, without credentials, under the key
621
+ * `hot-updater doctor` reads to tell the database `hot-updater init` saved
622
+ * from another (a convention CLAUDE.md documents).
623
+ */
624
+ const DATABASE_TARGET = Symbol.for("@hot-updater/database-target");
625
+ /** The account host a client reaches: its endpoint, or the connection string's. */
626
+ const accountHostOf = ({ endpoint, connectionString }) => {
627
+ const url = endpoint ?? connectionString?.match(/AccountEndpoint=([^;]+)/iu)?.[1];
628
+ try {
629
+ return url === void 0 ? void 0 : new URL(url).hostname;
630
+ } catch {
631
+ return;
632
+ }
610
633
  };
611
634
  /**
612
635
  * Hot Updater's database in one Azure Cosmos DB for NoSQL container,
@@ -615,16 +638,18 @@ const migrateCosmosDatabase = (config, plugins = []) => {
615
638
  * 20 GB.
616
639
  */
617
640
  const cosmosDatabase = (config) => {
618
- const { client, adapter } = adapterOf(config);
641
+ const { dispose, adapter } = adapterOf(config);
642
+ const host = accountHostOf(config);
619
643
  return {
620
644
  ...createEngineDatabase({
621
645
  name: "cosmosDatabase",
622
646
  adapter,
623
647
  aggregateBatching: config.aggregateBatching ?? {}
624
648
  }),
649
+ ...host === void 0 ? {} : { [DATABASE_TARGET]: `cosmos/${host}` },
625
650
  async dispose() {
626
651
  await adapter.dispose?.();
627
- client.dispose();
652
+ dispose();
628
653
  }
629
654
  };
630
655
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/azure",
3
3
  "type": "module",
4
- "version": "1.1.4",
4
+ "version": "1.2.0-rc.0",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "engines": {
7
7
  "node": "^22.23.4 || ^24.22.1 || >=26.12.1"
@@ -42,10 +42,10 @@
42
42
  "@azure/core-auth": "1.11.0",
43
43
  "@azure/cosmos": "4.10.1",
44
44
  "@azure/storage-blob": "12.34.0",
45
- "@hot-updater/server": "1.1.4",
46
- "@hot-updater/cli-tools": "1.1.4",
47
- "@hot-updater/plugin-core": "1.1.4",
48
- "@hot-updater/protocol": "1.1.4"
45
+ "@hot-updater/cli-tools": "1.2.0-rc.0",
46
+ "@hot-updater/plugin-core": "1.2.0-rc.0",
47
+ "@hot-updater/protocol": "1.2.0-rc.0",
48
+ "@hot-updater/server": "1.2.0-rc.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@azure/functions": "4.16.5",
@@ -53,7 +53,7 @@
53
53
  "execa": "9.5.2",
54
54
  "jszip": "^3.10.1",
55
55
  "vitest": "4.1.4",
56
- "@hot-updater/test-utils": "1.1.4"
56
+ "@hot-updater/test-utils": "1.2.0-rc.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsdown",