@hasna/contacts 0.7.0 → 0.8.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.
Files changed (39) hide show
  1. package/README.md +83 -13
  2. package/dist/cli/commands/core.d.ts.map +1 -1
  3. package/dist/cli/index.js +82 -33
  4. package/dist/cloud/http-storage.d.ts +20 -12
  5. package/dist/cloud/http-storage.d.ts.map +1 -1
  6. package/dist/cloud/resolver-inputs.d.ts +51 -0
  7. package/dist/cloud/resolver-inputs.d.ts.map +1 -0
  8. package/dist/db/paths.d.ts +4 -4
  9. package/dist/db/paths.d.ts.map +1 -1
  10. package/dist/generated/storage-kit/backend.d.ts +19 -0
  11. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  12. package/dist/generated/storage-kit/index.d.ts +2 -1
  13. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  14. package/dist/generated/storage-kit/migrations.d.ts +21 -0
  15. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  16. package/dist/generated/storage-kit/own.d.ts +11 -0
  17. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  18. package/dist/generated/storage-kit/pool.d.ts +5 -17
  19. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  20. package/dist/generated/storage-kit/query.d.ts +1 -1
  21. package/dist/generated/storage-kit/query.d.ts.map +1 -1
  22. package/dist/generated/storage-kit/tls.d.ts +30 -3
  23. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +105 -24
  27. package/dist/mcp/index.d.ts +9 -0
  28. package/dist/mcp/index.d.ts.map +1 -1
  29. package/dist/mcp/index.js +112 -18
  30. package/dist/mcp/startup-gate.d.ts +48 -0
  31. package/dist/mcp/startup-gate.d.ts.map +1 -0
  32. package/dist/sdk/index.d.ts +70 -3
  33. package/dist/sdk/index.d.ts.map +1 -1
  34. package/dist/sdk/index.js +990 -7
  35. package/dist/server/index.js +201 -43
  36. package/hasna.contract.json +20 -6
  37. package/package.json +3 -3
  38. package/dist/lib/config.d.ts +0 -7
  39. package/dist/lib/config.d.ts.map +0 -1
package/README.md CHANGED
@@ -14,33 +14,87 @@ npm install -g @hasna/contacts
14
14
  ## Configure the client
15
15
 
16
16
  Every CLI, MCP, and package data operation uses one authenticated HTTPS `/v1`
17
- authority. There is no built-in hosted URL and no local database fallback.
17
+ authority resolved by the shared `@hasna/contracts` client chain
18
+ (hasna/apps#1720), resolved fresh on every request. There is no local database
19
+ fallback and no per-app env chain of its own.
20
+
21
+ Once a contacts API key resolves from any tier, the authority defaults to the
22
+ fleet gateway `https://api.hasna.com/contacts` — no URL configuration is
23
+ needed on a station. The credential tiers, in order:
24
+
25
+ 1. explicit arguments / deliberate pointers — `HASNA_CONTACTS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_CONTACTS_API_KEY_REF` (secrets vault)
26
+ 2. macOS Keychain — `hasna.credentials.contacts.api-key` / `.api-url`, account `HASNA_STATION` → `hostname -s` → `$USER`
27
+ 3. disk — `~/.hasna/contacts/config/credentials` (owner-only 0400/0600, `HASNA_CONTACTS_API_KEY=…` or the `HASNA_CONTACTS_API_URL=…` alias)
28
+ 4. environment — `HASNA_CONTACTS_API_KEY`
29
+
30
+ | Env var | Meaning |
31
+ |---|---|
32
+ | `HASNA_CONTACTS_API_URL` | Explicit API base URL (HTTPS; overrides the fleet gateway). Legacy alias: `CONTACTS_API_URL`. |
33
+ | `HASNA_CONTACTS_API_KEY` | API key (env tier). Legacy alias: `CONTACTS_API_KEY`. |
34
+ | `HASNA_CONTACTS_API_KEY_OVERRIDE` | Deliberate per-run override that outranks the Keychain and disk. |
35
+ | `HASNA_CONTACTS_API_KEY_REF` | Secrets-vault pointer (`namespace/app/live/api_key`); terminal when unresolvable. |
36
+ | `HASNA_PROFILE` | Selects which identity (`credentials-<profile>`) the disk tier reads. |
37
+ | `HASNA_STATION` | Keychain account when set; else short hostname, then `$USER`. |
38
+ | `HASNA_HOME` | Replaces `~` for the `~/.hasna/…` credential/disk root. |
39
+ | `HASNA_CONFIG_HOME` | Replaces `~/.hasna/<app>/config` entirely. |
18
40
 
19
41
  ```bash
42
+ # Fully explicit:
20
43
  export HASNA_CONTACTS_API_URL="https://contacts.example.com"
21
- # Provision the API key through the @hasna/contracts credential chain. For
22
- # example, put HASNA_CONTACTS_API_KEY in the shared fleet/config credential chain,
23
- # or configure HASNA_CONTACTS_API_KEY_REF for the secrets client.
44
+ export HASNA_CONTACTS_API_KEY="…" # or configure the Keychain/disk tiers
24
45
  contacts connection --json
25
46
  ```
26
47
 
27
- An absent or invalid URL/key fails closed. `HASNA_CONTACTS_STORAGE_MODE`,
28
- `CONTACTS_STORAGE_MODE`, contacts DB-path variables, and contacts database URLs
29
- are rejected in client processes. PostgreSQL URLs belong only to
30
- `contacts-serve` and the migration task.
48
+ An absent URL and key fails closed: operations exit non-zero and never open a
49
+ local store; `contacts connection` reports `transport: "unconfigured"`.
50
+ The fail-closed message starts on the FIRST stderr line — it names what is
51
+ missing and where the credential should live (`HASNA_CONTACTS_API_KEY`, the
52
+ Keychain item, the credentials-file path), never a value.
53
+ `HASNA_CONTACTS_STORAGE_MODE`, `CONTACTS_STORAGE_MODE`, contacts DB-path
54
+ variables, and contacts database URLs are rejected in client processes.
55
+ PostgreSQL URLs belong only to `contacts-serve` and the migration task.
31
56
 
32
57
  ## CLI Usage
33
58
 
34
59
  ```bash
35
- contacts status # CLI version, API endpoint, storage mode, record counts
60
+ contacts status # CLI version, resolved /v1 authority + sources, storage mode, record counts
36
61
  contacts status --json
37
62
  contacts --help
38
63
  ```
39
64
 
40
- `contacts status` answers even on a box without an API key: an unconfigured
41
- client reports storage `unconfigured` (a failed request on a configured box
42
- reports storage `error` with the failure message) instead of crashing, so
43
- agents can observe the configuration drift the command exists to expose.
65
+ `contacts status` reports the authority the shared resolver actually decided
66
+ (`api`, the `/v1` base URL) and where each half came from — `api_url_source`,
67
+ `api_key_source`, `api_key_tier`: an env key name, a Keychain item reference,
68
+ a credentials-file path, or `default` for the fleet gateway; never a value.
69
+ It answers even on a box without an API key: an unconfigured client reports
70
+ storage `unconfigured` with the resolver's `issue` (a failed request on a
71
+ configured box reports storage `error` with the failure message) instead of
72
+ crashing, so agents can observe the configuration drift the command exists to
73
+ expose. `status` and `connection` are diagnostics and exit 0 with that report;
74
+ every data verb fails closed (non-zero exit, no local store).
75
+
76
+ ## SDK
77
+
78
+ ```ts
79
+ import { createContactsClient, ContactsV1Client } from "@hasna/contacts/sdk";
80
+
81
+ // Through the fleet resolver — the same @hasna/contracts chain the CLI and MCP
82
+ // server use: credential and authority resolved at construction, the key
83
+ // re-resolved on every request, the authority pinned. Nothing resolving throws.
84
+ const client = createContactsClient();
85
+ const { contacts } = await client.listContacts();
86
+
87
+ // Explicit pin: a caller-supplied baseUrl always requires a caller-supplied
88
+ // apiKey — the SDK never attaches an ambient credential to it.
89
+ const pinned = new ContactsV1Client({ baseUrl: "https://contacts.example.com", apiKey: "…" });
90
+ ```
91
+
92
+ `createContactsClient()` resolves the literal tiers only — an explicit
93
+ `apiKey`, `HASNA_CONTACTS_API_KEY_OVERRIDE`, the Keychain item, the
94
+ credentials file, `HASNA_CONTACTS_API_KEY`. The secrets-vault pointer
95
+ `HASNA_CONTACTS_API_KEY_REF` is refused with
96
+ `CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED` (the SDK resolves synchronously per
97
+ request and cannot complete a vault lookup); the CLI and MCP server accept it.
44
98
 
45
99
 
46
100
  ## Audiences, consent, and suppression
@@ -76,6 +130,22 @@ suppressed addresses; the audience `--policy` (`opt_in`, `opt_out`,
76
130
  contacts-mcp
77
131
  ```
78
132
 
133
+ `contacts-mcp` fails closed at startup: with no credential resolvable through
134
+ the chain above it exits non-zero BEFORE the stdio transport is connected or
135
+ the HTTP port is bound — an `initialize` request is never answered by an
136
+ unauthenticated server — and creates nothing under the app home. The first
137
+ stderr line names where the credential should live (the Keychain item, the
138
+ credentials-file path, `HASNA_CONTACTS_API_KEY`), never a value.
139
+ `--help` / `--version` answer ahead of the gate; every tool still re-resolves
140
+ the credential per request once the server is up.
141
+
142
+ The deliberate tiers are honoured at the gate as well. The secrets-vault
143
+ pointer `HASNA_CONTACTS_API_KEY_REF` is dereferenced once at startup (the
144
+ chain alone only checks the pointer's shape): a pointer the vault cannot
145
+ complete, a `HASNA_PROFILE` whose credentials file is missing, or an unsafe
146
+ (not owner-only) credentials file is a one-line refusal naming the pointer,
147
+ the file, or the item — never resolved around, never a stack trace.
148
+
79
149
  ## HTTP mode
80
150
 
81
151
  Long-lived Streamable HTTP transport (stateless, bind `127.0.0.1` only):
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAg4B5E"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAu5B5E"}
package/dist/cli/index.js CHANGED
@@ -3621,6 +3621,13 @@ function parseMatcherExpression(value, label) {
3621
3621
  negated: false
3622
3622
  };
3623
3623
  }
3624
+ function webhookTargetPolicyFromEnv() {
3625
+ const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
3626
+ if (!value)
3627
+ return;
3628
+ const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3629
+ return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
3630
+ }
3624
3631
  var DEFAULT_EVENT_LIST_LIMIT = 100;
3625
3632
  function parseJsonObject(value, fallback) {
3626
3633
  if (!value)
@@ -3646,7 +3653,7 @@ function parseHeaders(values) {
3646
3653
  function createClient(options) {
3647
3654
  if (options.createClient)
3648
3655
  return options.createClient();
3649
- return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
3656
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
3650
3657
  }
3651
3658
  function print(value, json, text) {
3652
3659
  if (json)
@@ -3727,6 +3734,8 @@ function registerChannelCommands(program, options) {
3727
3734
  metadata: parseJsonObject(actionOptions.metadata, {})
3728
3735
  }, { honorFilters: actionOptions.honorFilters });
3729
3736
  print(result, json, `${result.status}: ${result.channelId}`);
3737
+ if (result.status === "failed")
3738
+ process.exitCode = 1;
3730
3739
  } catch (error) {
3731
3740
  fail(error, json);
3732
3741
  }
@@ -3840,6 +3849,7 @@ import chalk2 from "chalk";
3840
3849
 
3841
3850
  // src/cloud/http-storage.ts
3842
3851
  import {
3852
+ ClientTransportConfigurationError,
3843
3853
  createHasnaHttpTransport,
3844
3854
  resolveCredential,
3845
3855
  resolveClientTransport as resolveSharedClientTransport
@@ -3929,6 +3939,27 @@ function assertConfigurationUnchanged(env, expected) {
3929
3939
  invalid();
3930
3940
  }
3931
3941
 
3942
+ // src/cloud/resolver-inputs.ts
3943
+ import {
3944
+ clientTransportEnvKeys as clientTransportEnvKeys2,
3945
+ credentialOverrideEnvKey,
3946
+ credentialPointerEnvKey,
3947
+ CREDENTIAL_PROFILE_ENV_KEY
3948
+ } from "@hasna/contracts/client";
3949
+ var CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
3950
+ function isAmbientContactsEnv(env) {
3951
+ if (typeof process !== "undefined" && env === process.env)
3952
+ return true;
3953
+ return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
3954
+ }
3955
+ function contactsResolverCredentials(env, credentials = {}) {
3956
+ const keychain = { ...credentials.keychain };
3957
+ if (keychain.enabled === undefined && keychain.run === undefined) {
3958
+ keychain.enabled = isAmbientContactsEnv(env);
3959
+ }
3960
+ return { ...credentials, keychain };
3961
+ }
3962
+
3932
3963
  // src/cloud/http-storage.ts
3933
3964
  var RETIRED_CLIENT_SELECTOR_KEYS = [
3934
3965
  "HASNA_CONTACTS_STORAGE_MODE",
@@ -3964,37 +3995,44 @@ class ContactsClientConfigurationError extends Error {
3964
3995
  this.name = "ContactsClientConfigurationError";
3965
3996
  }
3966
3997
  }
3967
- function unconfiguredResolution(resolution, issue) {
3998
+ function unconfiguredResolution(issue, resolution, warning = null) {
3968
3999
  return {
3969
4000
  transport: "unconfigured",
3970
4001
  baseUrl: null,
3971
- apiUrlSource: resolution.apiUrlSource,
3972
- apiKeyPresent: resolution.apiKeyPresent,
3973
- apiKeySource: resolution.apiKeySource,
3974
- apiKeyTier: resolution.apiKeyTier,
4002
+ apiUrlSource: resolution?.apiUrlSource ?? null,
4003
+ apiKeyPresent: resolution?.apiKeyPresent ?? false,
4004
+ apiKeySource: resolution?.apiKeySource ?? null,
4005
+ apiKeyTier: resolution?.apiKeyTier ?? null,
3975
4006
  configured: false,
3976
4007
  misconfigured: true,
3977
4008
  issue,
3978
- warning: null
4009
+ warning
3979
4010
  };
3980
4011
  }
3981
- function resolveContactsClientTransport(name, env = process.env) {
4012
+ function resolveContactsClientTransport(name, env = process.env, credentials = {}) {
3982
4013
  if (name !== "contacts") {
3983
4014
  throw new ContactsClientConfigurationError("CONTACTS_CLIENT_NAME_INVALID", "This resolver only accepts the contacts app slug.");
3984
4015
  }
3985
4016
  assertNoRetiredClientSelectors(env);
3986
4017
  const stamp = clientConfigurationStamp(env);
3987
- const resolution = resolveSharedClientTransport(name, env);
3988
- assertConfigurationUnchanged(env, stamp);
3989
- if (resolution.transport !== "http" || !resolution.baseUrl) {
3990
- const issue = resolution.misconfigured ? "The configured contacts API URL or credential is invalid or incomplete." : "HASNA_CONTACTS_API_URL and a resolvable contacts API key are required; no local fallback exists.";
3991
- return unconfiguredResolution(resolution, issue);
4018
+ let resolution;
4019
+ try {
4020
+ resolution = resolveSharedClientTransport(name, env, {
4021
+ credentials: contactsResolverCredentials(env, credentials)
4022
+ });
4023
+ } catch (error) {
4024
+ assertConfigurationUnchanged(env, stamp);
4025
+ if (error instanceof ClientTransportConfigurationError) {
4026
+ return unconfiguredResolution(error.message, null);
4027
+ }
4028
+ throw error;
3992
4029
  }
4030
+ assertConfigurationUnchanged(env, stamp);
3993
4031
  try {
3994
4032
  assertHttpsBaseUrl(resolution.baseUrl);
3995
4033
  } catch (error) {
3996
4034
  return {
3997
- ...unconfiguredResolution(resolution, error instanceof Error ? error.message : String(error)),
4035
+ ...unconfiguredResolution(error instanceof Error ? error.message : String(error), resolution),
3998
4036
  apiKeyPresent: resolution.apiKeyPresent
3999
4037
  };
4000
4038
  }
@@ -4008,7 +4046,7 @@ function resolveContactsClientTransport(name, env = process.env) {
4008
4046
  configured: true,
4009
4047
  misconfigured: false,
4010
4048
  issue: null,
4011
- warning: null
4049
+ warning: resolution.warning
4012
4050
  };
4013
4051
  }
4014
4052
  function resourcePath(resource) {
@@ -4062,20 +4100,21 @@ function createStorageClient(name, transport) {
4062
4100
  }
4063
4101
  };
4064
4102
  }
4065
- function resolveContactsStorageClient(name, env = process.env) {
4066
- const resolution = resolveContactsClientTransport(name, env);
4103
+ function resolveContactsStorageClient(name, env = process.env, credentials = {}) {
4104
+ const resolution = resolveContactsClientTransport(name, env, credentials);
4067
4105
  if (!resolution.configured || !resolution.baseUrl) {
4068
4106
  throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", `${resolution.issue ?? "The contacts API client is not configured."} ` + "Configure HASNA_CONTACTS_API_URL and a contacts API key; the client will not read or create a local SQLite database.");
4069
4107
  }
4070
4108
  const baseUrl = resolution.baseUrl;
4109
+ const chainOptions = contactsResolverCredentials(env, credentials);
4071
4110
  const request = async (method, path, body, opts) => {
4072
4111
  const stamp = clientConfigurationStamp(env);
4073
4112
  const snapshot = { ...env };
4074
- const current = resolveContactsClientTransport(name, snapshot);
4113
+ const current = resolveContactsClientTransport(name, snapshot, chainOptions);
4075
4114
  if (!current.configured || current.baseUrl !== baseUrl) {
4076
4115
  throw new ContactsClientConfigurationError("CONTACTS_AUTHORITY_CHANGED", "Client authority changed or disappeared; construct a new client before sending data.");
4077
4116
  }
4078
- const credential = resolveCredential(name, snapshot);
4117
+ const credential = resolveCredential(name, snapshot, chainOptions);
4079
4118
  if (!credential)
4080
4119
  throw new ContactsClientConfigurationError("CONTACTS_API_NOT_CONFIGURED", "No credential is available.");
4081
4120
  assertConfigurationUnchanged(env, stamp);
@@ -5375,12 +5414,22 @@ function collect(val, prev) {
5375
5414
  }
5376
5415
  function registerCoreCommands(program2, version) {
5377
5416
  program2.command("status").description("Show CLI version, API endpoint, storage mode, and record counts").option("--json", "Output as JSON").action(async (opts) => {
5378
- const baseUrl = process.env["HASNA_CONTACTS_API_URL"]?.trim();
5379
- const apiLine = baseUrl ? baseUrl : "(not configured \u2014 set HASNA_CONTACTS_API_URL plus a contacts API key)";
5417
+ let apiLine = "(not configured \u2014 set HASNA_CONTACTS_API_URL plus a contacts API key)";
5418
+ let apiUrlSource = null;
5419
+ let apiKeySource = null;
5420
+ let apiKeyTier = null;
5421
+ let issue = null;
5380
5422
  let counts;
5381
5423
  let storage;
5382
5424
  let failure;
5383
5425
  try {
5426
+ const resolution = resolveContactsClientTransport("contacts");
5427
+ apiUrlSource = resolution.apiUrlSource;
5428
+ apiKeySource = resolution.apiKeySource;
5429
+ apiKeyTier = resolution.apiKeyTier;
5430
+ issue = resolution.issue;
5431
+ if (resolution.configured && resolution.baseUrl)
5432
+ apiLine = resolution.baseUrl;
5384
5433
  const store = getStore();
5385
5434
  const [contacts, companies] = await Promise.all([
5386
5435
  store.listContacts({ limit: 1 }),
@@ -5400,7 +5449,11 @@ function registerCoreCommands(program2, version) {
5400
5449
  service: "contacts",
5401
5450
  version,
5402
5451
  api: apiLine,
5452
+ api_url_source: apiUrlSource,
5453
+ api_key_source: apiKeySource,
5454
+ api_key_tier: apiKeyTier,
5403
5455
  storage,
5456
+ ...issue !== null ? { issue } : {},
5404
5457
  ...failure !== undefined ? { error: failure } : {},
5405
5458
  ...counts ? { counts } : {}
5406
5459
  };
@@ -5410,7 +5463,13 @@ function registerCoreCommands(program2, version) {
5410
5463
  }
5411
5464
  console.log(chalk2.bold(`contacts v${version}`));
5412
5465
  console.log(`API: ${apiLine}`);
5466
+ if (apiUrlSource)
5467
+ console.log(`API URL source: ${apiUrlSource}`);
5468
+ if (apiKeySource)
5469
+ console.log(`API key source: ${apiKeySource}${apiKeyTier ? ` (${apiKeyTier})` : ""}`);
5413
5470
  console.log(`Storage: ${storage}`);
5471
+ if (issue !== null)
5472
+ console.log(chalk2.yellow(`Issue: ${issue}`));
5414
5473
  if (failure !== undefined)
5415
5474
  console.log(chalk2.red(`Error: ${failure}`));
5416
5475
  if (counts) {
@@ -12177,10 +12236,8 @@ import chalk7 from "chalk";
12177
12236
  import { homedir as pathsResolverHomedir2 } from "os";
12178
12237
  import { join as pathsResolverJoin2 } from "path";
12179
12238
  var PATHS_RESOLVER_KIND_ENV2 = {
12180
- config: "HASNA_CONFIG_HOME",
12181
12239
  data: "HASNA_DATA_HOME",
12182
- state: "HASNA_STATE_HOME",
12183
- cache: "HASNA_CACHE_HOME"
12240
+ state: "HASNA_STATE_HOME"
12184
12241
  };
12185
12242
  var PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
12186
12243
  function pathsResolverAssertApp2(app) {
@@ -12206,24 +12263,17 @@ function pathsResolverBaseDir2(kind, options) {
12206
12263
  const platform = options.platform ?? process.platform;
12207
12264
  if (platform === "darwin") {
12208
12265
  switch (kind) {
12209
- case "config":
12210
12266
  case "data":
12211
12267
  return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
12212
- case "cache":
12213
- return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
12214
12268
  case "state":
12215
12269
  return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
12216
12270
  }
12217
12271
  }
12218
12272
  switch (kind) {
12219
- case "config":
12220
- return pathsResolverJoin2(home, ".config", "hasna");
12221
12273
  case "data":
12222
12274
  return pathsResolverJoin2(home, ".local", "share", "hasna");
12223
12275
  case "state":
12224
12276
  return pathsResolverJoin2(home, ".local", "state", "hasna");
12225
- case "cache":
12226
- return pathsResolverJoin2(home, ".cache", "hasna");
12227
12277
  }
12228
12278
  }
12229
12279
  function pathsResolverResolve2(kind, options) {
@@ -12409,8 +12459,7 @@ registerEventCommands2(program, { source: "contacts", eventsCommandName: "hasna-
12409
12459
  program.parseAsync(process.argv).catch((err) => {
12410
12460
  const message = err instanceof Error ? err.message : String(err);
12411
12461
  const isApiUnavailable = err instanceof Error && err.name === "ApiUnavailableError";
12412
- console.error(`
12413
- ` + (isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
12462
+ console.error((isApiUnavailable ? chalk8.yellow(message) : chalk8.red(message)) + `
12414
12463
  `);
12415
12464
  process.exit(1);
12416
12465
  });
@@ -2,18 +2,20 @@
2
2
  * Canonical contacts client transport.
3
3
  *
4
4
  * Public clients have exactly one data path: an authenticated HTTPS `/v1`
5
- * authority. The server URL is always explicit; this module never composes or
6
- * guesses a hosted URL. API-key resolution and per-request rotation are owned
7
- * by `@hasna/contracts/client`, so key material is never exposed by status
8
- * objects or cached in this package.
5
+ * authority, resolved by `@hasna/contracts/client` the ONE fleet resolver.
6
+ * The authority defaults to the fleet gateway `https://api.hasna.com/contacts`
7
+ * once any credential tier resolves, and `HASNA_CONTACTS_API_URL`, the
8
+ * Keychain `api-url` item, or the credentials file override it. API-key
9
+ * resolution and per-request rotation are owned by the shared resolver, so
10
+ * key material is never exposed by status objects or cached in this package.
9
11
  *
10
12
  * SQLite, PostgreSQL DSNs, and storage/deployment modes are not client
11
13
  * transports. A stale selector is a configuration error, not a reason to read
12
14
  * a different data set.
13
15
  */
14
- import { type ClientTransportResolution as SharedClientTransportResolution, type HasnaHttpTransport, type HasnaRequestOptions, type QueryParams } from "@hasna/contracts/client";
15
- export type Env = Record<string, string | undefined>;
16
- export type { QueryParams };
16
+ import { type CredentialChainOptions, type CredentialTier, type HasnaHttpTransport, type HasnaRequestOptions, type QueryParams } from "@hasna/contracts/client";
17
+ import type { Env } from "./resolver-inputs.js";
18
+ export type { Env, QueryParams };
17
19
  export declare const RETIRED_CLIENT_SELECTOR_KEYS: readonly ["HASNA_CONTACTS_STORAGE_MODE", "CONTACTS_STORAGE_MODE", "HASNA_CONTACTS_MODE", "CONTACTS_MODE", "HASNA_CONTACTS_DB_PATH", "CONTACTS_DB_PATH", "HASNA_CONTACTS_DATABASE_URL", "CONTACTS_DATABASE_URL"];
18
20
  export declare class ContactsClientConfigurationError extends Error {
19
21
  readonly code: string;
@@ -25,17 +27,23 @@ export interface ClientTransportResolution {
25
27
  apiUrlSource: string | null;
26
28
  apiKeyPresent: boolean;
27
29
  apiKeySource: string | null;
28
- apiKeyTier: SharedClientTransportResolution["apiKeyTier"];
30
+ apiKeyTier: CredentialTier | null;
29
31
  configured: boolean;
30
32
  misconfigured: boolean;
31
33
  issue: string | null;
32
34
  warning: string | null;
33
35
  }
34
36
  /**
35
- * Resolve value-free connection diagnostics. This function does not invent a
36
- * default authority and never reports a local transport as usable.
37
+ * Resolve value-free connection diagnostics.
38
+ *
39
+ * @hasna/contracts 1.0.2 THROWS `ClientTransportConfigurationError` for every
40
+ * incomplete or invalid configuration — nothing resolves to a local or
41
+ * partial transport any more. A refusal of that class is reported here as
42
+ * `unconfigured` (never as a usable transport); the shared resolver's own
43
+ * hard refusals — a blank declared variable, a conflict, an unreadable
44
+ * credential file — stay hard errors, exactly as the resolver throws them.
37
45
  */
38
- export declare function resolveContactsClientTransport(name: string, env?: Env): ClientTransportResolution;
46
+ export declare function resolveContactsClientTransport(name: string, env?: Env, credentials?: CredentialChainOptions): ClientTransportResolution;
39
47
  export interface StorageClient {
40
48
  readonly name: string;
41
49
  readonly baseUrl: string;
@@ -55,5 +63,5 @@ export interface ResolveStorageClientResult {
55
63
  resolution: ClientTransportResolution;
56
64
  }
57
65
  /** Build the sole contacts client. Any incomplete configuration is terminal. */
58
- export declare function resolveContactsStorageClient(name: string, env?: Env): ResolveStorageClientResult;
66
+ export declare function resolveContactsStorageClient(name: string, env?: Env, credentials?: CredentialChainOptions): ResolveStorageClientResult;
59
67
  //# sourceMappingURL=http-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-storage.d.ts","sourceRoot":"","sources":["../../src/cloud/http-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAIL,KAAK,yBAAyB,IAAI,+BAA+B,EACjE,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AACrD,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,eAAO,MAAM,4BAA4B,iNAS/B,CAAC;AA2BX,qBAAa,gCAAiC,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAInD;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,OAAO,GAAG,cAAc,CAAC;IACpC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,+BAA+B,CAAC,YAAY,CAAC,CAAC;IAC1D,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAoBD;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAiB,GAAG,yBAAyB,CAuC9G;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;IACvC,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5E,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9F,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG;QAAE,MAAM,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACzI,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CACvG;AAqBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,GAAG,aAAa,CAgC9F;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAiB,GAAG,0BAA0B,CA6C7G"}
1
+ {"version":3,"file":"http-storage.d.ts","sourceRoot":"","sources":["../../src/cloud/http-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The credential-chain seam between @hasna/contracts 1.0.2 and this package.
3
+ *
4
+ * Contacts never builds its own credential or authority chain: the CLI, the
5
+ * MCP server and the store all funnel through
6
+ * `resolveContactsClientTransport`, which hands @hasna/contracts the
7
+ * environment unchanged, per request, fresh. The one thing this module owns
8
+ * is the ambient gate for the resolver's Keychain tier (#1788).
9
+ *
10
+ * The Keychain belongs to the MACHINE, so the resolver consults it only when
11
+ * it is handed the live process environment (or a snapshot the resolver
12
+ * itself marked ambient) — a caller-built env object is the hermetic seam and
13
+ * must never drag a machine identity into a test. A copy made on the way in
14
+ * silently loses that gate, and the per-request re-resolution in
15
+ * `resolveContactsStorageClient` does make a copy: a snapshot of the env so a
16
+ * concurrent mutation cannot tear one resolution. A copy that drops the
17
+ * Keychain gate would, on a Mac station whose only credential is the Keychain
18
+ * item, turn every request into a fresh failure — or, with a credential on
19
+ * disk, into a DIFFERENT principal with no notice.
20
+ *
21
+ * So the ambient answer is decided HERE, on the ORIGINAL env, before any copy
22
+ * exists, and carried across as the documented `keychain.enabled` control.
23
+ * A caller's explicit `keychain` options always win; the injected `run`
24
+ * runner (which @hasna/contracts already treats as "enabled") is left alone.
25
+ */
26
+ import { type CredentialChainOptions } from "@hasna/contracts/client";
27
+ export type Env = Record<string, string | undefined>;
28
+ /**
29
+ * Is this the environment the machine's ambient credential stores belong to?
30
+ *
31
+ * The same test @hasna/contracts performs, run on the env BEFORE any copy
32
+ * exists — which is the whole point of asking here.
33
+ */
34
+ export declare function isAmbientContactsEnv(env: Env): boolean;
35
+ /** Every env name the resolver may consult for the contacts authority or
36
+ * credential — canonical names, the legacy aliases, the deliberate pointers,
37
+ * and the profile selector. */
38
+ export declare function contactsAuthorityEnvKeys(): string[];
39
+ /**
40
+ * The credential chain options a contacts surface hands the resolver.
41
+ *
42
+ * On the identity path (`env === process.env`, or a snapshot @hasna/contracts
43
+ * itself marked ambient) this passes through untouched and the resolver runs
44
+ * its own ambient test. On a CALLER-BUILT env — every hermetic test env, and
45
+ * the per-request snapshot in `resolveContactsStorageClient` — the Keychain
46
+ * tier would silently switch off the moment a copy is made; the caller's
47
+ * explicit controls still win, and otherwise the tier is pinned to what the
48
+ * ORIGINAL env was: ambient in, enabled; hermetic in, disabled.
49
+ */
50
+ export declare function contactsResolverCredentials(env: Env, credentials?: CredentialChainOptions): CredentialChainOptions;
51
+ //# sourceMappingURL=resolver-inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver-inputs.d.ts","sourceRoot":"","sources":["../../src/cloud/resolver-inputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAKL,KAAK,sBAAsB,EAE5B,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAMrD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAGtD;AAED;;+BAE+B;AAC/B,wBAAgB,wBAAwB,IAAI,MAAM,EAAE,CASnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,GAAG,EACR,WAAW,GAAE,sBAA2B,GACvC,sBAAsB,CAMxB"}
@@ -1,4 +1,4 @@
1
- export type PathKind = "config" | "data" | "state" | "cache";
1
+ export type PathKind = "data" | "state";
2
2
  export interface PathsResolverOptions {
3
3
  app: string;
4
4
  internal?: boolean;
@@ -9,12 +9,12 @@ export interface PathsResolverOptions {
9
9
  export declare function dataDir(options: PathsResolverOptions): string;
10
10
  export declare function stateDir(options: PathsResolverOptions): string;
11
11
  /**
12
- * The @hasna/paths data root for retained server/legacy utilities. It creates
13
- * only the requested target directory and never scans or adopts older homes.
12
+ * The data root for retained server/legacy utilities. It creates only the
13
+ * requested target directory and never scans or adopts older homes.
14
14
  */
15
15
  export declare function getDataDir(): string;
16
16
  /**
17
- * The @hasna/paths state root for retained server/legacy utilities.
17
+ * The state root for retained server/legacy utilities.
18
18
  */
19
19
  export declare function getStateDir(): string;
20
20
  export declare function getDbPath(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/db/paths.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAS7D,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AA0DD,wBAAgB,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE7D;AACD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE9D;AAWD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAKpC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/db/paths.ts"],"names":[],"mappings":"AA6BA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAOxC,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAmDD,wBAAgB,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE7D;AACD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAE9D;AAWD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAKpC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC"}
@@ -0,0 +1,19 @@
1
+ export declare const SERVER_DATA_BACKENDS: readonly ["postgresql"];
2
+ export type ServerDataBackend = (typeof SERVER_DATA_BACKENDS)[number];
3
+ export type Env = Record<string, string | undefined>;
4
+ /** Upper-snake env token for an app name, e.g. `todos` -> `TODOS`. */
5
+ export declare function envToken(name: string): string;
6
+ export interface ServerDataBackendEnvKeys {
7
+ databaseUrlKeys: string[];
8
+ }
9
+ export declare function serverDataBackendEnvKeys(name: string): ServerDataBackendEnvKeys;
10
+ export interface ServerDataBackendResolution {
11
+ backend: ServerDataBackend;
12
+ source: string;
13
+ databaseUrlPresent: boolean;
14
+ databaseUrlSource: string;
15
+ }
16
+ export declare function resolveServerDataBackend(name: string, env?: Env): ServerDataBackendResolution;
17
+ /** Resolve the required PostgreSQL URL without logging it. */
18
+ export declare function resolveDatabaseUrl(name: string, env?: Env): string;
19
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/backend.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,oBAAoB,yBAA0B,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAErD,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,CAK/E;AA8CD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,GAAiB,GACrB,2BAA2B,CAS7B;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAiB,GAAG,MAAM,CAG/E"}
@@ -1,4 +1,5 @@
1
- export declare const KIT_VERSION = "0.4.2";
1
+ export declare const KIT_VERSION = "1.0.2";
2
+ export * from "./backend.js";
2
3
  export * from "./tls.js";
3
4
  export * from "./query.js";
4
5
  export * from "./pool.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/index.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/index.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,WAAW,UAAU,CAAC;AAEnC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
@@ -27,11 +27,32 @@ export declare function checksumSql(sql: string): string;
27
27
  export declare function defineMigration(id: string, sql: string): Migration;
28
28
  export interface MigrationRunnerOptions {
29
29
  ledgerTable?: string;
30
+ /**
31
+ * Applied-ledger rows whose ids the build ACKNOWLEDGES as non-reproducible
32
+ * history: a migration that was applied to the ledger by an out-of-band
33
+ * operation or by a build whose id scheme no longer exists, so no current
34
+ * source can reproduce its id or its SQL.
35
+ *
36
+ * An acknowledged id:
37
+ * - passes the downgrade guard (it IS recognized — as history),
38
+ * - is never checksum-compared (its SQL is gone, so no checksum can be
39
+ * computed for it; storing an arbitrary placeholder in `checksum` is
40
+ * what the prod ledger already holds for such rows),
41
+ * - is never re-applied and never re-inserted (it is already in the
42
+ * ledger; the plan covers declared migrations only).
43
+ *
44
+ * The list is EXPLICIT and OPT-IN: an acknowledged id may not also be a
45
+ * declared migration (enforced at construction), and any OTHER applied row
46
+ * unknown to the build still fails the downgrade guard. Every declared
47
+ * migration keeps its checksum bind unchanged.
48
+ */
49
+ acknowledgedLegacyIds?: readonly string[];
30
50
  }
31
51
  export declare class MigrationLedger {
32
52
  private readonly client;
33
53
  private readonly migrations;
34
54
  private readonly ledgerTable;
55
+ private readonly acknowledgedLegacyIds;
35
56
  constructor(client: TypedQueryClient, migrations: readonly Migration[], options?: MigrationRunnerOptions);
36
57
  ensureLedger(): Promise<void>;
37
58
  listApplied(): Promise<AppliedMigration[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/migrations.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,2EAA2E;AAC3E,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAElE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAElE;AAQD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGlB,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EACjD,OAAO,GAAE,sBAA2B;IAUhC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,WAAW,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAKlC,WAAW;IAWzB,oEAAoE;IACpE,OAAO,CAAC,SAAS;IAsBjB,yEAAyE;IACnE,OAAO,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC;CAiBzE;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAEjB"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/generated/storage-kit/migrations.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAuEnD,2EAA2E;AAC3E,eAAO,MAAM,8BAA8B,sBAAsB,CAAC;AAElE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAElE;AAQD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAL7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAsB;gBAGzC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EACjD,OAAO,GAAE,sBAA2B;IA2BhC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B,WAAW,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAKlC,WAAW;IAWzB,oEAAoE;IACpE,OAAO,CAAC,SAAS;IA0BjB,yEAAyE;IACnE,OAAO,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC;CAqBzE;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAEjB"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Read `key` from `source` only when `source` OWNS it.
3
+ *
4
+ * Returns `undefined` for an inherited property and for a null/undefined or
5
+ * non-object source, so a guarded read is a drop-in for `source?.[key]` that
6
+ * cannot be answered by the prototype chain.
7
+ */
8
+ export declare function ownProp<T>(source: unknown, key: string): T | undefined;
9
+ /** `ownProp` narrowed to a string, so a polluted non-string cannot slip through. */
10
+ export declare function ownString(source: unknown, key: string): string | undefined;
11
+ //# sourceMappingURL=own.d.ts.map