@isomorph.ai/app-sdk 1.13.0 → 1.13.2-rc.2026101101

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.
package/README.md CHANGED
@@ -183,6 +183,10 @@ and no second GET is sent. When the refresh fails, `identity.current()`
183
183
  answers `null` and listeners hear `null`, as before. On the page's own origin
184
184
  the identity GET is never renewed (it is the renewal). (1.5.3)
185
185
 
186
+ ## External OTP users
187
+
188
+ Use `user.id` for ownership and database row rules. External users can sign in with a phone number and have no email. `user.name`, `user.phone`, `user.email` and the `emailVerified`/`phoneVerified` flags describe their profile; an optional contact is not verified automatically. `user.directory` identifies `company` or `external`. Database transactions also expose `current_setting('harbour.directory', true)` when a row rule needs that distinction.
189
+
186
190
  ## The signed-in user's groups
187
191
 
188
192
  `user.groups` — the company groups your identity provider sent at sign-in; use
package/dist/index.d.ts CHANGED
@@ -13,7 +13,12 @@ export declare class IsomorphError extends Error {
13
13
  }
14
14
  export interface IsomorphUser {
15
15
  id: string;
16
- email: string;
16
+ email?: string;
17
+ name?: string;
18
+ phone?: string;
19
+ directory?: 'company' | 'external';
20
+ emailVerified?: boolean;
21
+ phoneVerified?: boolean;
17
22
  /**
18
23
  * The company groups your identity provider sent at sign-in, `[]` when it
19
24
  * sent none. Use them for in-app roles: who may open the app at all is
package/dist/runtime.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare const GET_RUN_ACTION = "isomorph-jobs-get";
11
11
  export declare const CANCEL_RUN_ACTION = "isomorph-jobs-cancel";
12
12
  export interface ActionContext {
13
13
  signal: AbortSignal;
14
- user: Pick<IsomorphUser, 'id' | 'email' | 'groups' | 'role'>;
14
+ user: IsomorphUser;
15
15
  tenant: string;
16
16
  app: string;
17
17
  requestId: string;
package/dist/runtime.js CHANGED
@@ -54,6 +54,7 @@ import { createServer } from 'node:http';
54
54
  import { readFile, readdir, stat } from 'node:fs/promises';
55
55
  import { register } from 'node:module';
56
56
  import { join, resolve } from 'node:path';
57
+ import { realpathSync } from 'node:fs';
57
58
  import { pathToFileURL } from 'node:url';
58
59
  import { createClient, IsomorphError } from './index.js';
59
60
  import { userGroupsFromHeader } from './user-groups.js';
@@ -488,11 +489,16 @@ export class Runtime {
488
489
  const controller = new AbortController();
489
490
  const abort = () => controller.abort();
490
491
  response.once('close', abort);
492
+ const directory = header(request, 'X-Harbour-Verified-Directory');
491
493
  const user = {
492
494
  id: header(request, 'X-Harbour-Verified-User-ID'),
493
495
  email: header(request, 'X-Harbour-Verified-User-Email'),
494
496
  groups: userGroupsFromHeader(header(request, 'X-Harbour-Verified-User-Groups')),
495
497
  role: userRole(header(request, 'X-Harbour-Verified-User-Role')),
498
+ ...(directory === 'company' || directory === 'external' ? { directory } : {}),
499
+ ...(header(request, 'X-Harbour-Verified-User-Name') ? { name: header(request, 'X-Harbour-Verified-User-Name') } : {}),
500
+ ...(header(request, 'X-Harbour-Verified-User-Phone') ? { phone: header(request, 'X-Harbour-Verified-User-Phone') } : {}),
501
+ ...(directory === 'external' ? { emailVerified: header(request, 'X-Harbour-Verified-Email-Verified') === 'true', phoneVerified: header(request, 'X-Harbour-Verified-Phone-Verified') === 'true' } : {}),
496
502
  };
497
503
  const streams = header(request, 'accept').toLowerCase().includes(PROGRESS_MEDIA_TYPE);
498
504
  let steps = 0;
@@ -629,7 +635,19 @@ export function runJobFromArguments(argv = process.argv.slice(2)) {
629
635
  }
630
636
  return undefined;
631
637
  }
632
- const entry = process.argv[1] ? pathToFileURL(process.argv[1]).href : '';
638
+ // Node resolves this module's URL through any symlink in its path, while
639
+ // process.argv[1] is the path the starter typed. On macOS /tmp and /var are
640
+ // symlinks, so a gate or `isomorph dev` started from an app root under them
641
+ // handed a path whose URL never matched, and the runtime loaded as a library
642
+ // and exited 0 without a word (the gate reported "exited before it
643
+ // answered", 2026-10-06). Compare the real path, as Node did.
644
+ const entryPath = process.argv[1] ? (() => { try {
645
+ return realpathSync(process.argv[1]);
646
+ }
647
+ catch {
648
+ return process.argv[1];
649
+ } })() : '';
650
+ const entry = entryPath ? pathToFileURL(entryPath).href : '';
633
651
  if (import.meta.url === entry) {
634
652
  // Extensionless relative imports in the app's server code resolve to
635
653
  // their .ts files (runtime-hooks.ts); registered before any app module
@@ -130,7 +130,7 @@ export declare class SupabaseCompatQueryBuilder<T, Result = T[], Head extends bo
130
130
  }
131
131
  export interface SupabaseCompatUser {
132
132
  id: string;
133
- email: string;
133
+ email?: string;
134
134
  app_metadata: Record<string, unknown>;
135
135
  /** Isomorph's per-app user record (`IsomorphUser.appUser`) when it is an object. */
136
136
  user_metadata: Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.2-rc.2026101101",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",