ofplatform-web 0.1.0-alpha.0 → 0.1.0-alpha.2

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.
@@ -3,6 +3,8 @@ type TableSchemaMap = Record<string, TableSchema>;
3
3
  export interface IndexedDbAdapterOptions {
4
4
  dbName?: string;
5
5
  initialDbVersion?: number;
6
+ tableNamePrefix?: string;
7
+ tableNameMapper?: (tableName: string) => string;
6
8
  }
7
9
  export interface IndexedDbBackupPayload {
8
10
  schema: 'ofplatform-web-indexeddb-backup-v1';
@@ -19,9 +21,16 @@ export interface IndexedDbImportOptions {
19
21
  export declare class IndexedDbAdapter implements DbAdapter {
20
22
  private readonly dbName;
21
23
  private readonly initialDbVersion?;
24
+ private readonly tableNamePrefix;
25
+ private tableNameResolver?;
22
26
  private dbPromise?;
27
+ private dbInstance?;
23
28
  private transactionDepth;
24
29
  constructor(options?: IndexedDbAdapterOptions);
30
+ setTableNameResolver(resolver: (logicalTableName: string) => string): void;
31
+ close(): Promise<void>;
32
+ closeSync(): void;
33
+ resolveTableName(tableName: string): string;
25
34
  private openDatabase;
26
35
  private getDb;
27
36
  private reopenWithUpgrade;
@@ -0,0 +1,27 @@
1
+ import type { HttpAdapter, HttpRequest, HttpResponse } from 'ofcore';
2
+ export type FetchImplementation = (input: string, init?: RequestInit) => Promise<Response>;
3
+ export interface FetchHttpAdapterOptions {
4
+ /**
5
+ * Override the fetch implementation. Defaults to `globalThis.fetch`.
6
+ * Useful for testing or environments where a custom fetch is required.
7
+ */
8
+ fetch?: FetchImplementation;
9
+ }
10
+ /**
11
+ * Browser-native HTTP adapter backed by the Fetch API.
12
+ *
13
+ * Uses `globalThis.fetch` by default, making it compatible with all modern
14
+ * browsers and browser-targeted runtimes (e.g. Vite/webpack bundles).
15
+ *
16
+ * Features:
17
+ * - Query parameter serialisation (null/undefined values are omitted)
18
+ * - Automatic `content-type: application/json` for requests with a body
19
+ * - Timeout support via `AbortController`
20
+ * - Response body parsed as JSON when `content-type` indicates JSON,
21
+ * otherwise returns raw text
22
+ */
23
+ export declare class FetchHttpAdapter implements HttpAdapter {
24
+ private readonly fetchImpl;
25
+ constructor(options?: FetchHttpAdapterOptions);
26
+ request<T = unknown>(request: HttpRequest): Promise<HttpResponse<T>>;
27
+ }
@@ -1 +1,2 @@
1
- export declare const OFPLATFORM_WEB_HTTP_ADAPTERS_READY = false;
1
+ export * from './FetchHttpAdapter';
2
+ export declare const OFPLATFORM_WEB_HTTP_ADAPTERS_READY = true;
@@ -0,0 +1,44 @@
1
+ import type { PlatformAdapter } from 'ofcore';
2
+ type SubtleCryptoLike = Pick<SubtleCrypto, 'digest'>;
3
+ export interface CryptoLike {
4
+ subtle: SubtleCryptoLike;
5
+ }
6
+ export interface WebPlatformAdapterOptions {
7
+ /**
8
+ * Environment variables exposed via getEnvVar.
9
+ * In a Vite application, pass `import.meta.env` here.
10
+ * Defaults to an empty map (no env vars available in browser context).
11
+ */
12
+ env?: Record<string, string | undefined>;
13
+ /**
14
+ * Override the Web Crypto implementation.
15
+ * Defaults to `globalThis.crypto`.
16
+ * Useful for testing or environments where a custom crypto is required.
17
+ */
18
+ crypto?: CryptoLike;
19
+ }
20
+ /**
21
+ * Browser-native PlatformAdapter using the Web Crypto API (SubtleCrypto).
22
+ *
23
+ * - `hashPin` → SHA-256 via `crypto.subtle.digest`, returns `sha256:<hex64>`
24
+ * - `verifyPin` → computes SHA-256 and compares; also handles legacy `sha256:<plaintext>`
25
+ * format (produced by DefaultPlatformAdapter placeholder).
26
+ * - `getEnvVar` → reads from the `env` map supplied at construction time.
27
+ * - `isOnline` → reads `navigator.onLine`, falls back to `true`.
28
+ *
29
+ * Legacy hash detection: a real SHA-256 hex is always exactly 64 lowercase hex
30
+ * characters after the `sha256:` prefix. Anything shorter/non-hex is treated as
31
+ * a legacy plaintext hash and compared directly, enabling a transparent migration
32
+ * from the placeholder implementation.
33
+ */
34
+ export declare class WebPlatformAdapter implements PlatformAdapter {
35
+ private readonly env;
36
+ private readonly cryptoImpl;
37
+ constructor(options?: WebPlatformAdapterOptions);
38
+ getEnvVar(key: string): string | undefined;
39
+ isOnline(): boolean;
40
+ hashPin(pin: string): Promise<string>;
41
+ verifyPin(pin: string, storedHash: string): Promise<boolean>;
42
+ private sha256Hex;
43
+ }
44
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './WebPlatformAdapter';
2
+ export declare const OFPLATFORM_WEB_PLATFORM_ADAPTERS_READY = true;
@@ -0,0 +1,79 @@
1
+ import type { SocketAdapter, SocketConnectOptions, SocketEventHandler } from 'ofcore';
2
+ /** Wire format for messages sent/received over the WebSocket channel. */
3
+ export interface WebSocketMessage {
4
+ event: string;
5
+ payload: unknown;
6
+ }
7
+ export interface WebSocketAdapterOptions {
8
+ /**
9
+ * Enable automatic reconnection on unexpected connection loss.
10
+ * @default true
11
+ */
12
+ reconnect?: boolean;
13
+ /**
14
+ * Maximum number of reconnection attempts before giving up.
15
+ * @default 5
16
+ */
17
+ maxReconnectAttempts?: number;
18
+ /**
19
+ * Initial delay between reconnection attempts in milliseconds.
20
+ * Grows exponentially on each subsequent attempt.
21
+ * @default 500
22
+ */
23
+ reconnectDelayMs?: number;
24
+ /**
25
+ * Maximum delay cap for exponential backoff in milliseconds.
26
+ * @default 30000
27
+ */
28
+ maxReconnectDelayMs?: number;
29
+ /**
30
+ * Override the WebSocket constructor. Defaults to `globalThis.WebSocket`.
31
+ * Pass a mock here for unit testing.
32
+ */
33
+ webSocket?: typeof WebSocket;
34
+ }
35
+ /**
36
+ * Browser-native WebSocket adapter implementing the `SocketAdapter` contract.
37
+ *
38
+ * ## Message protocol
39
+ * All messages on the wire are JSON-encoded `WebSocketMessage` objects:
40
+ * ```json
41
+ * { "event": "<event-name>", "payload": <any> }
42
+ * ```
43
+ * The server must emit and accept messages in this format.
44
+ *
45
+ * ## Headers limitation
46
+ * Browser WebSocket connections do not support custom HTTP request headers.
47
+ * Authentication tokens and other request-time metadata must be passed via
48
+ * `SocketConnectOptions.query` (appended as URL query parameters) or via
49
+ * an HTTP-only cookie.
50
+ *
51
+ * ## Reconnection
52
+ * Unexpected disconnections trigger exponential-backoff reconnection unless
53
+ * `options.reconnect` is set to `false` or the maximum attempt count is
54
+ * reached. Subscriptions survive reconnection automatically.
55
+ */
56
+ export declare class WebSocketAdapter implements SocketAdapter {
57
+ private ws;
58
+ private connectionOptions;
59
+ private readonly listeners;
60
+ private activeConnectionId;
61
+ private reconnectAttempts;
62
+ private reconnectTimeout;
63
+ private intentionalClose;
64
+ private readonly reconnect;
65
+ private readonly maxReconnectAttempts;
66
+ private readonly reconnectDelayMs;
67
+ private readonly maxReconnectDelayMs;
68
+ private readonly WebSocketCtor;
69
+ constructor(options?: WebSocketAdapterOptions);
70
+ connect(options: SocketConnectOptions): Promise<void>;
71
+ disconnect(): Promise<void>;
72
+ subscribe(eventName: string, handler: SocketEventHandler): () => void;
73
+ publish(eventName: string, payload: unknown): Promise<void>;
74
+ isConnected(): boolean;
75
+ private openConnection;
76
+ private handleMessage;
77
+ private scheduleReconnect;
78
+ private clearReconnectTimeout;
79
+ }
@@ -1 +1,2 @@
1
- export declare const OFPLATFORM_WEB_SOCKET_ADAPTERS_READY = false;
1
+ export * from './WebSocketAdapter';
2
+ export declare const OFPLATFORM_WEB_SOCKET_ADAPTERS_READY = true;
@@ -0,0 +1,33 @@
1
+ import type { StorageAdapter } from 'ofcore';
2
+ export type StorageBackend = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
3
+ export interface WebStorageAdapterOptions {
4
+ /**
5
+ * The underlying Web Storage instance to use.
6
+ * Defaults to `globalThis.localStorage`.
7
+ *
8
+ * Pass `globalThis.sessionStorage` for session-scoped storage,
9
+ * or a custom object implementing the Storage interface for testing.
10
+ */
11
+ storage?: StorageBackend;
12
+ }
13
+ /**
14
+ * Browser Web Storage adapter (`localStorage` / `sessionStorage`) implementing
15
+ * the `StorageAdapter` contract.
16
+ *
17
+ * Operations are synchronous — the interface allows both sync and async return
18
+ * types and this implementation returns synchronously for maximum performance.
19
+ *
20
+ * @example
21
+ * // Persistent storage (survives page reload)
22
+ * const storage = new WebStorageAdapter();
23
+ *
24
+ * // Session-scoped storage (cleared when tab closes)
25
+ * const storage = new WebStorageAdapter({ storage: sessionStorage });
26
+ */
27
+ export declare class WebStorageAdapter implements StorageAdapter {
28
+ private readonly store;
29
+ constructor(options?: WebStorageAdapterOptions);
30
+ getItem(key: string): string | null;
31
+ setItem(key: string, value: string): void;
32
+ removeItem(key: string): void;
33
+ }
@@ -1 +1,2 @@
1
- export declare const OFPLATFORM_WEB_STORAGE_ADAPTERS_READY = false;
1
+ export * from './WebStorageAdapter';
2
+ export declare const OFPLATFORM_WEB_STORAGE_ADAPTERS_READY = true;
@@ -0,0 +1,57 @@
1
+ import { type AuthoritySessionEnvelope, type CachedIdentityEntry } from 'ofauth-shared-core';
2
+ import { type StorageBackend } from '../adapters/storage/WebStorageAdapter';
3
+ type CacheRecord = {
4
+ identity?: CachedIdentityEntry;
5
+ envelope?: AuthoritySessionEnvelope;
6
+ };
7
+ export type WebServerAuthoritativeAuthHostSupportOptions = {
8
+ serverBaseUrl?: string;
9
+ cacheKey: string;
10
+ targetTenantId?: string;
11
+ targetBranchId?: string;
12
+ targetDomainId?: string;
13
+ storage?: StorageBackend;
14
+ fetchImpl?: typeof fetch;
15
+ isOnline?: () => boolean;
16
+ verifyOfflineSecret: (params: {
17
+ principal: string;
18
+ secret: string;
19
+ }) => Promise<boolean>;
20
+ };
21
+ export type WebServerAuthoritativeLoginResult = {
22
+ status: 'authenticated';
23
+ mode?: 'online' | 'offline';
24
+ } | {
25
+ status: 'rejected';
26
+ reasonCode?: string;
27
+ };
28
+ export type RestoredAuthEnvelope = {
29
+ principal: string;
30
+ envelope: AuthoritySessionEnvelope;
31
+ };
32
+ export declare class WebServerAuthoritativeAuthHostSupport {
33
+ private readonly options;
34
+ private readonly serverBaseUrl;
35
+ private readonly cacheKey;
36
+ private readonly targetTenantId;
37
+ private readonly targetBranchId;
38
+ private readonly targetDomainId;
39
+ private readonly storage;
40
+ private readonly fetchImpl;
41
+ private readonly isOnlineResolver;
42
+ private readonly serverBridge;
43
+ constructor(options: WebServerAuthoritativeAuthHostSupportOptions);
44
+ get isServerConfigured(): boolean;
45
+ loadCacheMap(): Record<string, CacheRecord>;
46
+ saveCacheMap(next: Record<string, CacheRecord>): void;
47
+ readCacheRecord(principal: string): CacheRecord | null;
48
+ writeCacheRecord(principal: string, record: CacheRecord): void;
49
+ clearCachedSessionEnvelope(principal: string): void;
50
+ clearAllCachedSessionEnvelopes(): void;
51
+ login(principal: string, secret: string): Promise<WebServerAuthoritativeLoginResult>;
52
+ logout(principal: string): Promise<void>;
53
+ restoreCachedEnvelope(): Promise<RestoredAuthEnvelope | null>;
54
+ getStoredAccessToken(principal: string): string;
55
+ getStoredEnvelope(principal: string): AuthoritySessionEnvelope | null;
56
+ }
57
+ export {};
@@ -0,0 +1 @@
1
+ export * from './WebServerAuthoritativeAuthHostSupport';
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export * from './adapters/db';
2
2
  export * from './adapters/http';
3
+ export * from './adapters/platform';
3
4
  export * from './adapters/socket';
4
5
  export * from './adapters/storage';
6
+ export * from './auth';
5
7
  export interface WebPlatformModuleStatus {
6
8
  platform: 'web';
7
9
  ready: boolean;
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- var x="__ofcore_meta__",S="__schema_version__",P="__table_schemas__";function T(c){return typeof structuredClone=="function"?structuredClone(c):JSON.parse(JSON.stringify(c))}function A(c){return!!(c&&typeof c=="object"&&!Array.isArray(c)&&"and"in c&&Array.isArray(c.and))}function R(c){return!!(c&&typeof c=="object"&&!Array.isArray(c)&&"or"in c&&Array.isArray(c.or))}function f(c){return new Promise((e,t)=>{c.onsuccess=()=>e(c.result),c.onerror=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB request failed"))}})}function D(c){return new Promise((e,t)=>{c.oncomplete=()=>e(),c.onerror=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB transaction failed"))},c.onabort=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB transaction aborted"))}})}var v=class{constructor(e={}){this.transactionDepth=0;var t;this.dbName=(t=e.dbName)!=null?t:"ofcore-app",this.initialDbVersion=e.initialDbVersion}async openDatabase(e){let t=typeof e=="number"?indexedDB.open(this.dbName,e):indexedDB.open(this.dbName);return t.onupgradeneeded=()=>{let r=t.result;r.objectStoreNames.contains(x)||r.createObjectStore(x,{keyPath:"key"})},new Promise((r,n)=>{t.onsuccess=()=>{let i=t.result;i.onversionchange=()=>i.close(),r(i)},t.onerror=()=>{let i=t.error;if((i==null?void 0:i.name)==="VersionError"){n(new Error(`IndexedDB version mismatch for "${this.dbName}": requested version is lower than existing database version`));return}n(i!=null?i:new Error("Failed to open IndexedDB"))},t.onblocked=()=>n(new Error("IndexedDB open blocked by another tab/process"))})}async getDb(){return this.dbPromise||(this.dbPromise=(async()=>{try{return await this.openDatabase(this.initialDbVersion)}catch(e){if(this.initialDbVersion!==void 0&&e instanceof Error&&e.message.includes("version mismatch"))return this.openDatabase();throw e}})()),this.dbPromise}async reopenWithUpgrade(e){let t=await this.getDb(),r=t.version+1;t.close(),this.dbPromise=void 0;let n=indexedDB.open(this.dbName,r);n.onupgradeneeded=()=>{let s=n.result;s.objectStoreNames.contains(x)||s.createObjectStore(x,{keyPath:"key"}),e(s)};let i=await new Promise((s,a)=>{n.onsuccess=()=>{let o=n.result;o.onversionchange=()=>o.close(),s(o)},n.onerror=()=>{var o;return a((o=n.error)!=null?o:new Error("Failed to upgrade IndexedDB"))},n.onblocked=()=>a(new Error("IndexedDB upgrade blocked by another tab/process"))});return this.dbPromise=Promise.resolve(i),i}async withStore(e,t,r){let i=(await this.getDb()).transaction(e,t),s=i.objectStore(e),a=await r(s);return await D(i),a}async getMetaValue(e,t){return(await this.getDb()).objectStoreNames.contains(x)?this.withStore(x,"readonly",async n=>{var s;let i=await f(n.get(e));return(s=i==null?void 0:i.value)!=null?s:t}):t}async setMetaValue(e,t){await this.withStore(x,"readwrite",async r=>{await f(r.put({key:e,value:t}))})}async getTableSchemas(){return this.getMetaValue(P,{})}async setTableSchemas(e){await this.setMetaValue(P,e)}async tableExists(e){return(await this.getDb()).objectStoreNames.contains(e)}async ensureTable(e){await this.tableExists(e)||await this.reopenWithUpgrade(t=>{t.objectStoreNames.contains(e)||t.createObjectStore(e,{keyPath:"id"})})}async listRows(e){return await this.tableExists(e)?this.withStore(e,"readonly",async r=>f(r.getAll())):[]}async putRows(e,t){await this.ensureTable(e);let n=(await this.getDb()).transaction(e,"readwrite"),i=n.objectStore(e);await f(i.clear());for(let s of t)await f(i.put(s));await D(n)}normalizeForComparison(e){if(e instanceof Date)return e.getTime();if(typeof e=="boolean")return e?1:0;if(Array.isArray(e))return e.map(t=>this.normalizeForComparison(t));if(e&&typeof e=="object")try{return JSON.stringify(e)}catch{return String(e)}return e}evalValueExpr(e,t,r,n="t0"){if(r.type==="column"){let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}return this.normalizeForComparison(r.value)}buildFieldCondition(e,t){if("field"in e)return{left:{type:"column",tableAlias:e.alias||t,field:e.field},operator:e.operator||"=",right:{type:"literal",value:e.value},...e.operator==="BETWEEN"?{}:{likeMode:e.likeMode}};if("left"in e)return e;throw new Error("Invalid field condition")}matchFilterExpression(e,t,r,n="t0"){if(A(r))return r.and.every(i=>this.matchFilterExpression(e,t,i,n));if(R(r))return r.or.some(i=>this.matchFilterExpression(e,t,i,n));if(r!==null&&typeof r=="object"&&!Array.isArray(r)){if("left"in r||"field"in r){let i=this.buildFieldCondition(r,n),s=this.evalValueExpr(e,t,i.left,n),a=this.evalValueExpr(e,t,i.right,n);if(i.operator==="IS NULL")return s==null;if(i.operator==="IS NOT NULL")return s!=null;if(i.operator==="IN"||i.operator==="NOT IN"){if(!Array.isArray(a))throw new Error("IN/NOT IN requires array as right value");let o=a.includes(s);return i.operator==="IN"?o:!o}if(i.operator==="BETWEEN")return!Array.isArray(a)||a.length!==2?!1:s>=a[0]&&s<=a[1];switch(i.operator){case"=":return s===a;case"!=":return s!==a;case"<":return s<a;case"<=":return s<=a;case">":return s>a;case">=":return s>=a;case"LIKE":{if(typeof s!="string"||typeof a!="string")return!1;let o=a.replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${o}$`,i.likeMode==="case-insensitive"?"i":"").test(s)}default:throw new Error(`Unsupported operator: ${i.operator}`)}}return Object.entries(r).every(([i,s])=>this.normalizeForComparison(e[i])===this.normalizeForComparison(s))}throw new Error(`Invalid filter expression: ${JSON.stringify(r)}`)}async simulateJoins(e,t){if(t.length===0)return e.map(i=>({record:i,joined:{}}));let r={};for(let i of t)r[i.alias]=await this.listRows(i.table);let n=[];for(let i of e){let s={},a=!0;for(let o of t){let u=r[o.alias]||[],l=null;if(o.conditions.length===1){let b=o.conditions[0];if(b.operator==="="&&b.left.type==="column"&&b.right.type==="column"){let w=b.left.tableAlias||"t0",m=b.right.type==="column"?b.right.tableAlias:void 0;if(w==="t0"&&b.left.type==="column"&&m===o.alias&&b.right.type==="column"){let p=this.normalizeForComparison(i[b.left.field]),g=b.right.field;l=u.find(h=>this.normalizeForComparison(h[g])===p)}}}if(!l&&(o.type==="inner"||!o.type)){a=!1;break}s[o.alias]=l!=null?l:null}a&&n.push({record:i,joined:s})}return n}extractField(e,t,r,n="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}extractAggregateField(e,t,r,n="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}async getSchemaVersion(){return this.getMetaValue(S,0)}async setSchemaVersion(e){await this.setMetaValue(S,e)}async addTable(e){await this.ensureTable(e.name);let t=await this.getTableSchemas();t[e.name]=T(e),await this.setTableSchemas(t)}async addColumn(e,t){var i;let r=await this.getTableSchemas(),n=(i=r[e])!=null?i:{name:e,columns:[],skipMetadataColumns:!0};n.columns.find(s=>s.name===t.name)||n.columns.push(T(t)),r[e]=n,await this.setTableSchemas(r)}async get(e,t){var i;return await this.tableExists(e)?typeof t=="string"?this.withStore(e,"readonly",async s=>{let a=await f(s.get(t));return a!=null?a:null}):(i=(await this.query(e,{...t,limit:1}))[0])!=null?i:null:null}async query(e,t={}){var l,b;if(!await this.tableExists(e))return[];let n=(l=t.mainAlias)!=null?l:"t0",i=await this.listRows(e),s=await this.simulateJoins(i,t.joins||[]);if(t.filters&&(s=s.filter(({record:w,joined:m})=>this.matchFilterExpression(w,m,t.filters,n))),t.aggregates&&t.aggregates.length>0){let w={};for(let m of t.aggregates){let p=s;m.filter&&(p=p.filter(({record:d,joined:E})=>this.matchFilterExpression(d,E,m.filter,n)));let g=p.map(({record:d,joined:E})=>this.extractAggregateField(d,E,m.field,n)),h=g.map(d=>Number(d)).filter(d=>Number.isFinite(d)),y=null;switch(m.function){case"COUNT":y=g.filter(d=>d!=null).length;break;case"SUM":y=h.reduce((d,E)=>d+E,0);break;case"AVG":y=h.length?h.reduce((d,E)=>d+E,0)/h.length:0;break;case"MIN":y=h.length?Math.min(...h):null;break;case"MAX":y=h.length?Math.max(...h):null;break;default:throw new Error(`Unsupported aggregate function: ${m.function}`)}w[m.as]=y}return[w]}let a=s.map(({record:w,joined:m})=>{if(!t.fields)return w;let p={};for(let[g,h]of Object.entries(t.fields)){let y=g===n?w:m[g]||{};for(let d of h)p[`${g}_${d}`]=y[d]}return p});t.sort&&t.sort.length>0&&a.sort((w,m)=>{for(let p of t.sort){let g=this.extractField(w,{},p.field,n),h=this.extractField(m,{},p.field,n);if(g===h)continue;if(g==null)return p.direction==="asc"?-1:1;if(h==null)return p.direction==="asc"?1:-1;let y=g>h?1:-1;return p.direction==="asc"?y:-y}return 0});let o=(b=t.offset)!=null?b:0,u=t.limit!=null?o+t.limit:void 0;return a.slice(o,u)}async create(e,t){await this.ensureTable(e);let r=t.id;if(!r||typeof r!="string")throw new Error(`create(${e}) requires string id`);return this.withStore(e,"readwrite",async n=>{if(await f(n.get(r)))throw new Error(`Record already exists: ${e}#${r}`);return await f(n.put(T(t))),T(t)})}async update(e,t,r){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);return this.withStore(e,"readwrite",async i=>{let s=await f(i.get(t));if(!s)throw new Error(`Record not found: ${e}#${t}`);let a={...s,...T(r),id:t};return await f(i.put(a)),T(a)})}async delete(e,t){await this.tableExists(e)&&await this.withStore(e,"readwrite",async n=>{await f(n.delete(t))})}async bulkCreate(e,t){await this.ensureTable(e);let n=(await this.getDb()).transaction(e,"readwrite"),i=n.objectStore(e),s=[];for(let a of t){if(!a.id||typeof a.id!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);let o=a.id;if(typeof o!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);if(await f(i.get(o)))throw n.abort(),new Error(`Record already exists: ${e}#${o}`);let l=T(a);await f(i.put(l)),s.push(l)}return await D(n),s}async bulkUpdate(e,t){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);let i=(await this.getDb()).transaction(e,"readwrite"),s=i.objectStore(e),a=[];for(let o of t){let u=await f(s.get(o.id));if(!u)throw i.abort(),new Error(`Record not found: ${e}#${o.id}`);let l={...u,...T(o.updates),id:o.id};await f(s.put(l)),a.push(T(l))}return await D(i),a}async dataStores(){let e=await this.getDb();return Array.from(e.objectStoreNames).filter(t=>t!==x)}async takeSnapshot(){let e=await this.dataStores(),t={};for(let r of e)t[r]=await this.listRows(r);return t}async restoreSnapshot(e){var r;let t=await this.dataStores();for(let n of t){let i=(r=e[n])!=null?r:[];await this.putRows(n,i)}}async transaction(e){let t=this.transactionDepth===0,r=null;t&&(r=await this.takeSnapshot()),this.transactionDepth+=1;try{let n=await e(this);return this.transactionDepth-=1,n}catch(n){throw this.transactionDepth-=1,t&&r&&await this.restoreSnapshot(r),n}}async exportBackupPayload(){let e=await this.getDb(),[t,r,n]=await Promise.all([this.getMetaValue(S,0),this.getTableSchemas(),this.takeSnapshot()]);return{schema:"ofplatform-web-indexeddb-backup-v1",dbName:this.dbName,dbVersion:e.version,exportedAt:new Date().toISOString(),schemaVersion:t,tableSchemas:r,tables:n}}async importBackupPayload(e,t={}){var i,s,a,o;if(e.schema!=="ofplatform-web-indexeddb-backup-v1")throw new Error("Invalid backup payload schema");let r=(i=t.clearExisting)!=null?i:!0,n=Object.keys((s=e.tables)!=null?s:{});for(let u of n)await this.ensureTable(u);if(r){let u=await this.dataStores();for(let l of u)n.includes(l)||await this.putRows(l,[])}for(let u of n){let l=Array.isArray(e.tables[u])?e.tables[u]:[];await this.putRows(u,l)}await this.setMetaValue(S,(a=e.schemaVersion)!=null?a:0),await this.setTableSchemas((o=e.tableSchemas)!=null?o:{})}};var N=!1;var _=!1;var C=!1;var O={platform:"web",ready:!0,notes:"IndexedDbAdapter is available; other adapter domains are scaffold-only."};export{v as IndexedDbAdapter,N as OFPLATFORM_WEB_HTTP_ADAPTERS_READY,_ as OFPLATFORM_WEB_SOCKET_ADAPTERS_READY,O as OFPLATFORM_WEB_STATUS,C as OFPLATFORM_WEB_STORAGE_ADAPTERS_READY};
1
+ var S="__ofcore_meta__",x="__schema_version__",E="__table_schemas__";function T(l){return typeof structuredClone=="function"?structuredClone(l):JSON.parse(JSON.stringify(l))}function W(l){return!!(l&&typeof l=="object"&&!Array.isArray(l)&&"and"in l&&Array.isArray(l.and))}function _(l){return!!(l&&typeof l=="object"&&!Array.isArray(l)&&"or"in l&&Array.isArray(l.or))}function w(l){return new Promise((e,t)=>{l.onsuccess=()=>e(l.result),l.onerror=()=>{var r;return t((r=l.error)!=null?r:new Error("IndexedDB request failed"))}})}function R(l){return new Promise((e,t)=>{l.oncomplete=()=>e(),l.onerror=()=>{var r;return t((r=l.error)!=null?r:new Error("IndexedDB transaction failed"))},l.onabort=()=>{var r;return t((r=l.error)!=null?r:new Error("IndexedDB transaction aborted"))}})}var P=class{constructor(e={}){this.transactionDepth=0;var t,r;this.dbName=(t=e.dbName)!=null?t:"ofcore-app",this.initialDbVersion=e.initialDbVersion,this.tableNamePrefix=(r=e.tableNamePrefix)!=null?r:"",this.tableNameResolver=e.tableNameMapper}setTableNameResolver(e){this.tableNameResolver=e}async close(){this.closeSync();let e=this.dbPromise;if(this.dbPromise=void 0,!!e)try{(await e).close()}catch{}}closeSync(){var e;try{(e=this.dbInstance)==null||e.close()}catch{}this.dbInstance=void 0,this.dbPromise=void 0}resolveTableName(e){return e===S?S:this.tableNameResolver?String(this.tableNameResolver(e)||"").trim()||e:!this.tableNamePrefix||e.startsWith(this.tableNamePrefix)?e:`${this.tableNamePrefix}${e}`}async openDatabase(e){let t=typeof e=="number"?indexedDB.open(this.dbName,e):indexedDB.open(this.dbName);return t.onupgradeneeded=()=>{let r=t.result;r.objectStoreNames.contains(S)||r.createObjectStore(S,{keyPath:"key"})},new Promise((r,i)=>{t.onsuccess=()=>{let n=t.result;n.onversionchange=()=>n.close(),this.dbInstance=n,r(n)},t.onerror=()=>{let n=t.error;if((n==null?void 0:n.name)==="VersionError"){i(new Error(`IndexedDB version mismatch for "${this.dbName}": requested version is lower than existing database version`));return}i(n!=null?n:new Error("Failed to open IndexedDB"))},t.onblocked=()=>i(new Error("IndexedDB open blocked by another tab/process"))})}async getDb(){return this.dbPromise||(this.dbPromise=(async()=>{try{return await this.openDatabase(this.initialDbVersion)}catch(e){if(this.initialDbVersion!==void 0&&e instanceof Error&&e.message.includes("version mismatch"))return this.openDatabase();throw e}})()),this.dbPromise}async reopenWithUpgrade(e){let t=await this.getDb(),r=t.version+1;t.close(),this.dbPromise=void 0;let i=indexedDB.open(this.dbName,r);i.onupgradeneeded=()=>{let s=i.result;s.objectStoreNames.contains(S)||s.createObjectStore(S,{keyPath:"key"}),e(s)};let n=await new Promise((s,o)=>{i.onsuccess=()=>{let c=i.result;c.onversionchange=()=>c.close(),this.dbInstance=c,s(c)},i.onerror=()=>{var c;return o((c=i.error)!=null?c:new Error("Failed to upgrade IndexedDB"))},i.onblocked=()=>o(new Error("IndexedDB upgrade blocked by another tab/process"))});return this.dbPromise=Promise.resolve(n),n}async withStore(e,t,r){let n=(await this.getDb()).transaction(e,t),s=n.objectStore(e),o=await r(s);return await R(n),o}async getMetaValue(e,t){return(await this.getDb()).objectStoreNames.contains(S)?this.withStore(S,"readonly",async i=>{var s;let n=await w(i.get(e));return(s=n==null?void 0:n.value)!=null?s:t}):t}async setMetaValue(e,t){await this.withStore(S,"readwrite",async r=>{await w(r.put({key:e,value:t}))})}async getTableSchemas(){return this.getMetaValue(E,{})}async setTableSchemas(e){await this.setMetaValue(E,e)}async tableExists(e){let t=this.resolveTableName(e);return(await this.getDb()).objectStoreNames.contains(t)}async ensureTable(e){let t=this.resolveTableName(e);await this.tableExists(e)||await this.reopenWithUpgrade(r=>{r.objectStoreNames.contains(t)||r.createObjectStore(t,{keyPath:"id"})})}async listRows(e){return await this.tableExists(e)?this.withStore(this.resolveTableName(e),"readonly",async r=>w(r.getAll())):[]}async putRows(e,t){await this.ensureTable(e);let r=this.resolveTableName(e),n=(await this.getDb()).transaction(r,"readwrite"),s=n.objectStore(r);await w(s.clear());for(let o of t)await w(s.put(o));await R(n)}normalizeForComparison(e){if(e instanceof Date)return e.getTime();if(typeof e=="boolean")return e?1:0;if(Array.isArray(e))return e.map(t=>this.normalizeForComparison(t));if(e&&typeof e=="object")try{return JSON.stringify(e)}catch{return String(e)}return e}evalValueExpr(e,t,r,i="t0"){if(r.type==="column"){let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}return this.normalizeForComparison(r.value)}buildFieldCondition(e,t){if("field"in e)return{left:{type:"column",tableAlias:e.alias||t,field:e.field},operator:e.operator||"=",right:{type:"literal",value:e.value},...e.operator==="BETWEEN"?{}:{likeMode:e.likeMode}};if("left"in e)return e;throw new Error("Invalid field condition")}matchFilterExpression(e,t,r,i="t0"){if(W(r))return r.and.every(n=>this.matchFilterExpression(e,t,n,i));if(_(r))return r.or.some(n=>this.matchFilterExpression(e,t,n,i));if(r!==null&&typeof r=="object"&&!Array.isArray(r)){if("left"in r||"field"in r){let n=this.buildFieldCondition(r,i),s=this.evalValueExpr(e,t,n.left,i),o=this.evalValueExpr(e,t,n.right,i);if(n.operator==="IS NULL")return s==null;if(n.operator==="IS NOT NULL")return s!=null;if(n.operator==="IN"||n.operator==="NOT IN"){if(!Array.isArray(o))throw new Error("IN/NOT IN requires array as right value");let c=o.includes(s);return n.operator==="IN"?c:!c}if(n.operator==="BETWEEN")return!Array.isArray(o)||o.length!==2?!1:s>=o[0]&&s<=o[1];switch(n.operator){case"=":return s===o;case"!=":return s!==o;case"<":return s<o;case"<=":return s<=o;case">":return s>o;case">=":return s>=o;case"LIKE":{if(typeof s!="string"||typeof o!="string")return!1;let c=o.replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${c}$`,n.likeMode==="case-insensitive"?"i":"").test(s)}default:throw new Error(`Unsupported operator: ${n.operator}`)}}return Object.entries(r).every(([n,s])=>this.normalizeForComparison(e[n])===this.normalizeForComparison(s))}throw new Error(`Invalid filter expression: ${JSON.stringify(r)}`)}async simulateJoins(e,t){if(t.length===0)return e.map(n=>({record:n,joined:{}}));let r={};for(let n of t)r[n.alias]=await this.listRows(n.table);let i=[];for(let n of e){let s={},o=!0;for(let c of t){let a=r[c.alias]||[],d=null;if(c.conditions.length===1){let h=c.conditions[0];if(h.operator==="="&&h.left.type==="column"&&h.right.type==="column"){let u=h.left.tableAlias||"t0",p=h.right.type==="column"?h.right.tableAlias:void 0;if(u==="t0"&&h.left.type==="column"&&p===c.alias&&h.right.type==="column"){let g=this.normalizeForComparison(n[h.left.field]),b=h.right.field;d=a.find(f=>this.normalizeForComparison(f[b])===g)}}}if(!d&&(c.type==="inner"||!c.type)){o=!1;break}s[c.alias]=d!=null?d:null}o&&i.push({record:n,joined:s})}return i}extractField(e,t,r,i="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}extractAggregateField(e,t,r,i="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}async getSchemaVersion(){return this.getMetaValue(x,0)}async setSchemaVersion(e){await this.setMetaValue(x,e)}async addTable(e){await this.ensureTable(e.name);let t=await this.getTableSchemas();t[e.name]=T(e),await this.setTableSchemas(t)}async addColumn(e,t){var n;let r=await this.getTableSchemas(),i=(n=r[e])!=null?n:{name:e,columns:[],skipMetadataColumns:!0};i.columns.find(s=>s.name===t.name)||i.columns.push(T(t)),r[e]=i,await this.setTableSchemas(r)}async get(e,t){var n;return await this.tableExists(e)?typeof t=="string"?this.withStore(this.resolveTableName(e),"readonly",async s=>{let o=await w(s.get(t));return o!=null?o:null}):(n=(await this.query(e,{...t,limit:1}))[0])!=null?n:null:null}async query(e,t={}){var d,h;if(!await this.tableExists(e))return[];let i=(d=t.mainAlias)!=null?d:"t0",n=await this.listRows(e),s=await this.simulateJoins(n,t.joins||[]);if(t.filters&&(s=s.filter(({record:u,joined:p})=>this.matchFilterExpression(u,p,t.filters,i))),t.aggregates&&t.aggregates.length>0){let u={};for(let p of t.aggregates){let g=s;p.filter&&(g=g.filter(({record:m,joined:v})=>this.matchFilterExpression(m,v,p.filter,i)));let b=g.map(({record:m,joined:v})=>this.extractAggregateField(m,v,p.field,i)),f=b.map(m=>Number(m)).filter(m=>Number.isFinite(m)),y=null;switch(p.function){case"COUNT":y=b.filter(m=>m!=null).length;break;case"SUM":y=f.reduce((m,v)=>m+v,0);break;case"AVG":y=f.length?f.reduce((m,v)=>m+v,0)/f.length:0;break;case"MIN":y=f.length?Math.min(...f):null;break;case"MAX":y=f.length?Math.max(...f):null;break;default:throw new Error(`Unsupported aggregate function: ${p.function}`)}u[p.as]=y}return[u]}t.sort&&t.sort.length>0&&s.sort((u,p)=>{for(let g of t.sort){let b=this.extractField(u.record,u.joined,g.field,i),f=this.extractField(p.record,p.joined,g.field,i);if(b===f)continue;if(b==null)return g.direction==="asc"?-1:1;if(f==null)return g.direction==="asc"?1:-1;let y=b>f?1:-1;return g.direction==="asc"?y:-y}return 0});let o=s.map(({record:u,joined:p})=>{var f;if(!t.fields)return u;if(((f=t.resultShape)!=null?f:"flat")==="nested"){let y={};for(let[m,v]of Object.entries(t.fields)){let A=m===i?u:p[m]||{},C={};for(let I of v)C[I]=A[I];y[m]=C}return y}let b={};for(let[y,m]of Object.entries(t.fields)){let v=y===i?u:p[y]||{};for(let A of m)b[`${y}_${A}`]=v[A]}return b}),c=(h=t.offset)!=null?h:0,a=t.limit!=null?c+t.limit:void 0;return o.slice(c,a)}async create(e,t){await this.ensureTable(e);let r=t.id;if(!r||typeof r!="string")throw new Error(`create(${e}) requires string id`);return this.withStore(this.resolveTableName(e),"readwrite",async i=>{if(await w(i.get(r)))throw new Error(`Record already exists: ${e}#${r}`);return await w(i.put(T(t))),T(t)})}async update(e,t,r){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);return this.withStore(this.resolveTableName(e),"readwrite",async n=>{let s=await w(n.get(t));if(!s)throw new Error(`Record not found: ${e}#${t}`);let o={...s,...T(r),id:t};return await w(n.put(o)),T(o)})}async delete(e,t){await this.tableExists(e)&&await this.withStore(this.resolveTableName(e),"readwrite",async i=>{await w(i.delete(t))})}async bulkCreate(e,t){await this.ensureTable(e);let r=this.resolveTableName(e),n=(await this.getDb()).transaction(r,"readwrite"),s=n.objectStore(r),o=[];for(let c of t){if(!c.id||typeof c.id!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);let a=c.id;if(typeof a!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);if(await w(s.get(a)))throw n.abort(),new Error(`Record already exists: ${e}#${a}`);let h=T(c);await w(s.put(h)),o.push(h)}return await R(n),o}async bulkUpdate(e,t){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);let i=this.resolveTableName(e),s=(await this.getDb()).transaction(i,"readwrite"),o=s.objectStore(i),c=[];for(let a of t){let d=await w(o.get(a.id));if(!d)throw s.abort(),new Error(`Record not found: ${e}#${a.id}`);let h={...d,...T(a.updates),id:a.id};await w(o.put(h)),c.push(T(h))}return await R(s),c}async dataStores(){let e=await this.getDb();return Array.from(e.objectStoreNames).filter(t=>t!==S)}async takeSnapshot(){let e=await this.dataStores(),t={};for(let r of e)t[r]=await this.listRows(r);return t}async restoreSnapshot(e){var r;let t=await this.dataStores();for(let i of t){let n=(r=e[i])!=null?r:[];await this.putRows(i,n)}}async transaction(e){let t=this.transactionDepth===0,r=null;t&&(r=await this.takeSnapshot()),this.transactionDepth+=1;try{let i=await e(this);return this.transactionDepth-=1,i}catch(i){throw this.transactionDepth-=1,t&&r&&await this.restoreSnapshot(r),i}}async exportBackupPayload(){let e=await this.getDb(),[t,r,i]=await Promise.all([this.getMetaValue(x,0),this.getTableSchemas(),this.takeSnapshot()]);return{schema:"ofplatform-web-indexeddb-backup-v1",dbName:this.dbName,dbVersion:e.version,exportedAt:new Date().toISOString(),schemaVersion:t,tableSchemas:r,tables:i}}async importBackupPayload(e,t={}){var n,s,o,c;if(e.schema!=="ofplatform-web-indexeddb-backup-v1")throw new Error("Invalid backup payload schema");let r=(n=t.clearExisting)!=null?n:!0,i=Object.keys((s=e.tables)!=null?s:{});for(let a of i)await this.ensureTable(a);if(r){let a=await this.dataStores();for(let d of a)i.includes(d)||await this.putRows(d,[])}for(let a of i){let d=Array.isArray(e.tables[a])?e.tables[a]:[];await this.putRows(a,d)}await this.setMetaValue(x,(o=e.schemaVersion)!=null?o:0),await this.setTableSchemas((c=e.tableSchemas)!=null?c:{})}};function j(l,e){if(!e||Object.keys(e).length===0)return l;let t=new URLSearchParams;for(let[i,n]of Object.entries(e))n!=null&&t.set(i,String(n));let r=t.toString();return r?l.includes("?")?`${l}&${r}`:`${l}?${r}`:l}var D=class{constructor(e={}){let t=e.fetch;if(t)this.fetchImpl=t;else{let r=globalThis.fetch;if(typeof r!="function")throw new Error("FetchHttpAdapter: globalThis.fetch is not available. Pass a fetch implementation via options.fetch.");this.fetchImpl=r.bind(globalThis)}}async request(e){var n,s;let t=j(e.url,e.query),r=typeof AbortController!="undefined"?new AbortController:null,i=r&&e.timeoutMs?setTimeout(()=>r.abort(),e.timeoutMs):null;try{let o=e.body!==void 0&&e.body!==null,c={...(n=e.headers)!=null?n:{}};o&&!c["content-type"]&&!c["Content-Type"]&&(c["content-type"]="application/json");let a=await this.fetchImpl(t,{method:e.method,headers:c,body:o?JSON.stringify(e.body):void 0,signal:r==null?void 0:r.signal}),d={};a.headers.forEach((p,g)=>{d[g.toLowerCase()]=p});let u=((s=d["content-type"])!=null?s:"").includes("application/json")?await a.json():await a.text();return{status:a.status,headers:d,data:u}}finally{i!==null&&clearTimeout(i)}}};var z=!0;var O=class{constructor(e={}){var t;if(this.env=(t=e.env)!=null?t:{},e.crypto)this.cryptoImpl=e.crypto;else{let r=globalThis.crypto;if(!(r!=null&&r.subtle))throw new Error("WebPlatformAdapter: globalThis.crypto.subtle is not available. Pass a crypto implementation via options.crypto.");this.cryptoImpl=r}}getEnvVar(e){return this.env[e]}isOnline(){let e=globalThis.navigator;return e&&typeof e.onLine!="undefined"?!!e.onLine:!0}async hashPin(e){return`sha256:${await this.sha256Hex(e)}`}async verifyPin(e,t){if(!t.startsWith("sha256:"))return!1;let r=t.slice(7);return/^[0-9a-f]{64}$/.test(r)?await this.sha256Hex(e)===r:r===e}async sha256Hex(e){let r=new TextEncoder().encode(e),i=await this.cryptoImpl.subtle.digest("SHA-256",r);return Array.from(new Uint8Array(i)).map(n=>n.toString(16).padStart(2,"0")).join("")}};var Y=!0;function V(l){let e=l.query;if(!e||Object.keys(e).length===0)return l.url;let r=new URLSearchParams(e).toString();return l.url.includes("?")?`${l.url}&${r}`:`${l.url}?${r}`}var B=class{constructor(e={}){this.ws=null;this.connectionOptions=null;this.listeners=new Map;this.activeConnectionId=0;this.reconnectAttempts=0;this.reconnectTimeout=null;this.intentionalClose=!1;var r,i,n,s,o;this.reconnect=(r=e.reconnect)!=null?r:!0,this.maxReconnectAttempts=Math.max(0,(i=e.maxReconnectAttempts)!=null?i:5),this.reconnectDelayMs=Math.max(0,(n=e.reconnectDelayMs)!=null?n:500),this.maxReconnectDelayMs=Math.max(this.reconnectDelayMs,(s=e.maxReconnectDelayMs)!=null?s:3e4);let t=(o=e.webSocket)!=null?o:globalThis.WebSocket;if(typeof t!="function")throw new Error("WebSocketAdapter: globalThis.WebSocket is not available. Pass a WebSocket constructor via options.webSocket.");this.WebSocketCtor=t}connect(e){if(this.clearReconnectTimeout(),this.intentionalClose=!0,this.ws)try{this.ws.close(1e3,"client reconnect")}catch{}finally{this.ws=null}this.connectionOptions=e,this.intentionalClose=!1,this.reconnectAttempts=0;let t=++this.activeConnectionId;return this.openConnection(e,t)}async disconnect(){this.intentionalClose=!0,this.clearReconnectTimeout(),this.activeConnectionId+=1,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}subscribe(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{let r=this.listeners.get(e);r&&(r.delete(t),r.size===0&&this.listeners.delete(e))}}async publish(e,t){var i,n;if(!this.ws||this.ws.readyState!==this.ws.OPEN)throw new Error(`WebSocketAdapter: cannot publish "${e}" \u2014 not connected (readyState=${(n=(i=this.ws)==null?void 0:i.readyState)!=null?n:"null"}).`);let r={event:e,payload:t};this.ws.send(JSON.stringify(r))}isConnected(){return this.ws!==null&&this.ws.readyState===this.ws.OPEN}openConnection(e,t){return new Promise((r,i)=>{let n=V(e),s;try{s=new this.WebSocketCtor(n)}catch(o){i(o instanceof Error?o:new Error(String(o)));return}s.onopen=()=>{if(t!==this.activeConnectionId){try{s.close(1e3,"stale connection")}catch{}r();return}this.ws=s,this.reconnectAttempts=0,r()},s.onerror=o=>{t===this.activeConnectionId&&(this.ws||i(new Error("WebSocketAdapter: connection failed")))},s.onclose=o=>{if(t!==this.activeConnectionId)return;this.ws===s&&(this.ws=null),!this.intentionalClose&&this.reconnect&&this.connectionOptions&&this.reconnectAttempts<this.maxReconnectAttempts&&this.scheduleReconnect()},s.onmessage=o=>{this.handleMessage(o.data)}})}handleMessage(e){if(typeof e!="string")return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!="object"||t===null||typeof t.event!="string")return;let{event:r,payload:i}=t,n=this.listeners.get(r);if(n)for(let s of n)try{s(i)}catch{}}scheduleReconnect(){this.clearReconnectTimeout();let e=++this.reconnectAttempts,t=Math.min(this.reconnectDelayMs*Math.pow(2,e-1),this.maxReconnectDelayMs);this.reconnectTimeout=setTimeout(()=>{if(!this.intentionalClose&&this.connectionOptions){let r=this.activeConnectionId;this.openConnection(this.connectionOptions,r).catch(()=>{})}},t)}clearReconnectTimeout(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}};var ee=!0;var k=class{constructor(e={}){if(e.storage)this.store=e.storage;else{let t=globalThis.localStorage;if(!t)throw new Error("WebStorageAdapter: globalThis.localStorage is not available. Pass a storage implementation via options.storage.");this.store=t}}getItem(e){return this.store.getItem(e)}setItem(e,t){this.store.setItem(e,t)}removeItem(e){this.store.removeItem(e)}};var ie=!0;import{AuthRejectionError as N,createServerAuthoritativeAuthBridge as $}from"ofauth-shared-core";function M(l,e){var i,n,s,o,c;let t=new Date().toISOString(),r=l&&typeof l=="object"?l:{};return{sessionId:String((i=r.sessionId)!=null?i:""),identityId:String((n=r.identityId)!=null?n:""),assuranceLevel:String((s=r.assuranceLevel)!=null?s:"basic"),issuedAt:String((o=r.issuedAt)!=null?o:t),expiresAt:String((c=r.expiresAt)!=null?c:e)}}var F=class{constructor(e){this.options=e;var i,n,s,o,c;if(this.serverBaseUrl=((i=e.serverBaseUrl)!=null?i:"").trim().replace(/\/+$/,""),this.cacheKey=e.cacheKey.trim(),this.targetTenantId=((n=e.targetTenantId)==null?void 0:n.trim())||"",this.targetBranchId=((s=e.targetBranchId)==null?void 0:s.trim())||"",this.targetDomainId=((o=e.targetDomainId)==null?void 0:o.trim())||"",this.storage=new k(e.storage?{storage:e.storage}:{}),e.fetchImpl)this.fetchImpl=e.fetchImpl;else if(typeof globalThis.fetch=="function")this.fetchImpl=globalThis.fetch.bind(globalThis);else throw new Error("WebServerAuthoritativeAuthHostSupport requires a fetch implementation");if(this.isOnlineResolver=(c=e.isOnline)!=null?c:(()=>typeof navigator=="undefined"||typeof navigator.onLine=="undefined"?!0:!!navigator.onLine),!this.serverBaseUrl){this.serverBridge=null;return}let t={login:async({principal:a,secret:d})=>{var g,b,f,y;let h=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal:a,secret:d,...this.targetTenantId?{targetTenantId:this.targetTenantId}:{},...this.targetBranchId?{targetBranchId:this.targetBranchId}:{},...this.targetDomainId?{targetDomainId:this.targetDomainId}:{}})}),u=await h.json().catch(()=>({}));if(!h.ok)throw new N(String((g=u.code)!=null?g:"AUTH_UNAUTHORIZED"));let p=new Date(Date.now()+15*6e4).toISOString();return{accessToken:String((b=u.accessToken)!=null?b:""),refreshToken:String((f=u.refreshToken)!=null?f:""),session:M(u.session,p),activeContext:(y=u.activeContext)!=null?y:null,activeRoleRef:u.activeRoleRef==null?"":String(u.activeRoleRef)}},refresh:async({refreshToken:a})=>{var p,g,b,f;let d=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})}),h=await d.json().catch(()=>({}));if(!d.ok)throw new N(String((p=h.code)!=null?p:"AUTH_UNAUTHORIZED"));let u=new Date(Date.now()+15*6e4).toISOString();return{accessToken:String((g=h.accessToken)!=null?g:""),refreshToken:String((b=h.refreshToken)!=null?b:""),session:M(h.session,u),activeContext:(f=h.activeContext)!=null?f:null}},logout:async({accessToken:a,refreshToken:d})=>{let h={"Content-Type":"application/json"};a&&(h.Authorization=`Bearer ${a}`),await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/logout`,{method:"POST",headers:h,body:JSON.stringify({refreshToken:d})}).catch(()=>{})}},r={getCachedIdentity:async a=>{var d,h;return(h=(d=this.readCacheRecord(a))==null?void 0:d.identity)!=null?h:null},setCachedIdentity:async a=>{let d=this.readCacheRecord(a.principal);this.writeCacheRecord(a.principal,{...d,identity:a})},clearCachedIdentity:async a=>{let d=this.readCacheRecord(a);d&&this.writeCacheRecord(a,{...d,identity:void 0})},getSessionEnvelope:async a=>{var d,h;return(h=(d=this.readCacheRecord(a))==null?void 0:d.envelope)!=null?h:null},setSessionEnvelope:async(a,d)=>{let h=this.readCacheRecord(a);this.writeCacheRecord(a,{...h,envelope:d})},clearSessionEnvelope:async a=>{let d=this.readCacheRecord(a);d&&this.writeCacheRecord(a,{...d,envelope:void 0})}};this.serverBridge=$({authority:t,cache:r,isOnline:()=>this.isOnlineResolver(),verifyOfflineSecret:this.options.verifyOfflineSecret})}get isServerConfigured(){return this.serverBaseUrl.length>0}loadCacheMap(){try{let e=this.storage.getItem(this.cacheKey);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}saveCacheMap(e){try{this.storage.setItem(this.cacheKey,JSON.stringify(e))}catch{}}readCacheRecord(e){var r;return(r=this.loadCacheMap()[e])!=null?r:null}writeCacheRecord(e,t){let r=this.loadCacheMap(),i={};t.identity&&(i.identity=t.identity),t.envelope&&(i.envelope=t.envelope),!i.identity&&!i.envelope?delete r[e]:r[e]=i,this.saveCacheMap(r)}clearCachedSessionEnvelope(e){let t=this.readCacheRecord(e);t&&this.writeCacheRecord(e,{...t,envelope:void 0})}clearAllCachedSessionEnvelopes(){let e=this.loadCacheMap();for(let t of Object.keys(e)){let r=e[t];r&&(e[t]=r.identity?{identity:r.identity}:{},e[t].identity||delete e[t])}this.saveCacheMap(e)}async login(e,t){var i,n;if(!this.serverBridge)return{status:"rejected",reasonCode:"AUTH_BRIDGE_NOT_CONFIGURED"};let r=await this.serverBridge.login({principal:e,secret:t,verifyMethod:"pin"});return r.status==="authenticated"?{status:"authenticated",mode:(i=r.mode)!=null?i:"online"}:{status:"rejected",reasonCode:(n=r.reasonCode)!=null?n:"AUTH_UNAUTHORIZED"}}async logout(e){this.serverBridge&&await this.serverBridge.logout(e)}async restoreCachedEnvelope(){var t,r,i;let e=this.loadCacheMap();for(let n of Object.keys(e)){let s=(t=e[n])==null?void 0:t.envelope;if(!s)continue;if(this.isOnlineResolver()&&s.refreshToken&&this.serverBaseUrl)try{let c=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:s.refreshToken})}),a=await c.json().catch(()=>({}));if(c.ok&&a.accessToken){let d={...s,accessToken:String(a.accessToken),refreshToken:a.refreshToken?String(a.refreshToken):s.refreshToken,activeContext:(i=(r=a.activeContext)!=null?r:s.activeContext)!=null?i:null};return this.writeCacheRecord(n,{...e[n],envelope:d}),{principal:n,envelope:d}}}catch{}let o=Date.parse(s.expiresAt);if(Number.isFinite(o)&&o>Date.now())return{principal:n,envelope:s}}return null}getStoredAccessToken(e){var t,r,i;return(i=(r=(t=this.readCacheRecord(e))==null?void 0:t.envelope)==null?void 0:r.accessToken)!=null?i:""}getStoredEnvelope(e){var t,r;return(r=(t=this.readCacheRecord(e))==null?void 0:t.envelope)!=null?r:null}};var ue={platform:"web",ready:!0,notes:"All adapter domains implemented: IndexedDbAdapter (db), FetchHttpAdapter (http), WebPlatformAdapter (platform), WebSocketAdapter (socket), WebStorageAdapter (storage)."};export{D as FetchHttpAdapter,P as IndexedDbAdapter,z as OFPLATFORM_WEB_HTTP_ADAPTERS_READY,Y as OFPLATFORM_WEB_PLATFORM_ADAPTERS_READY,ee as OFPLATFORM_WEB_SOCKET_ADAPTERS_READY,ue as OFPLATFORM_WEB_STATUS,ie as OFPLATFORM_WEB_STORAGE_ADAPTERS_READY,O as WebPlatformAdapter,F as WebServerAuthoritativeAuthHostSupport,B as WebSocketAdapter,k as WebStorageAdapter};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var P=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var I=(o,e)=>{for(var t in e)P(o,t,{get:e[t],enumerable:!0})},N=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of F(e))!k.call(o,n)&&n!==t&&P(o,n,{get:()=>e[n],enumerable:!(r=R(e,n))||r.enumerable});return o};var V=o=>N(P({},"__esModule",{value:!0}),o);var M={};I(M,{IndexedDbAdapter:()=>v,OFPLATFORM_WEB_HTTP_ADAPTERS_READY:()=>z,OFPLATFORM_WEB_SOCKET_ADAPTERS_READY:()=>W,OFPLATFORM_WEB_STATUS:()=>C,OFPLATFORM_WEB_STORAGE_ADAPTERS_READY:()=>J});module.exports=V(M);var x="__ofcore_meta__",S="__schema_version__",A="__table_schemas__";function T(o){return typeof structuredClone=="function"?structuredClone(o):JSON.parse(JSON.stringify(o))}function _(o){return!!(o&&typeof o=="object"&&!Array.isArray(o)&&"and"in o&&Array.isArray(o.and))}function B(o){return!!(o&&typeof o=="object"&&!Array.isArray(o)&&"or"in o&&Array.isArray(o.or))}function f(o){return new Promise((e,t)=>{o.onsuccess=()=>e(o.result),o.onerror=()=>{var r;return t((r=o.error)!=null?r:new Error("IndexedDB request failed"))}})}function D(o){return new Promise((e,t)=>{o.oncomplete=()=>e(),o.onerror=()=>{var r;return t((r=o.error)!=null?r:new Error("IndexedDB transaction failed"))},o.onabort=()=>{var r;return t((r=o.error)!=null?r:new Error("IndexedDB transaction aborted"))}})}var v=class{constructor(e={}){this.transactionDepth=0;var t;this.dbName=(t=e.dbName)!=null?t:"ofcore-app",this.initialDbVersion=e.initialDbVersion}async openDatabase(e){let t=typeof e=="number"?indexedDB.open(this.dbName,e):indexedDB.open(this.dbName);return t.onupgradeneeded=()=>{let r=t.result;r.objectStoreNames.contains(x)||r.createObjectStore(x,{keyPath:"key"})},new Promise((r,n)=>{t.onsuccess=()=>{let i=t.result;i.onversionchange=()=>i.close(),r(i)},t.onerror=()=>{let i=t.error;if((i==null?void 0:i.name)==="VersionError"){n(new Error(`IndexedDB version mismatch for "${this.dbName}": requested version is lower than existing database version`));return}n(i!=null?i:new Error("Failed to open IndexedDB"))},t.onblocked=()=>n(new Error("IndexedDB open blocked by another tab/process"))})}async getDb(){return this.dbPromise||(this.dbPromise=(async()=>{try{return await this.openDatabase(this.initialDbVersion)}catch(e){if(this.initialDbVersion!==void 0&&e instanceof Error&&e.message.includes("version mismatch"))return this.openDatabase();throw e}})()),this.dbPromise}async reopenWithUpgrade(e){let t=await this.getDb(),r=t.version+1;t.close(),this.dbPromise=void 0;let n=indexedDB.open(this.dbName,r);n.onupgradeneeded=()=>{let s=n.result;s.objectStoreNames.contains(x)||s.createObjectStore(x,{keyPath:"key"}),e(s)};let i=await new Promise((s,a)=>{n.onsuccess=()=>{let c=n.result;c.onversionchange=()=>c.close(),s(c)},n.onerror=()=>{var c;return a((c=n.error)!=null?c:new Error("Failed to upgrade IndexedDB"))},n.onblocked=()=>a(new Error("IndexedDB upgrade blocked by another tab/process"))});return this.dbPromise=Promise.resolve(i),i}async withStore(e,t,r){let i=(await this.getDb()).transaction(e,t),s=i.objectStore(e),a=await r(s);return await D(i),a}async getMetaValue(e,t){return(await this.getDb()).objectStoreNames.contains(x)?this.withStore(x,"readonly",async n=>{var s;let i=await f(n.get(e));return(s=i==null?void 0:i.value)!=null?s:t}):t}async setMetaValue(e,t){await this.withStore(x,"readwrite",async r=>{await f(r.put({key:e,value:t}))})}async getTableSchemas(){return this.getMetaValue(A,{})}async setTableSchemas(e){await this.setMetaValue(A,e)}async tableExists(e){return(await this.getDb()).objectStoreNames.contains(e)}async ensureTable(e){await this.tableExists(e)||await this.reopenWithUpgrade(t=>{t.objectStoreNames.contains(e)||t.createObjectStore(e,{keyPath:"id"})})}async listRows(e){return await this.tableExists(e)?this.withStore(e,"readonly",async r=>f(r.getAll())):[]}async putRows(e,t){await this.ensureTable(e);let n=(await this.getDb()).transaction(e,"readwrite"),i=n.objectStore(e);await f(i.clear());for(let s of t)await f(i.put(s));await D(n)}normalizeForComparison(e){if(e instanceof Date)return e.getTime();if(typeof e=="boolean")return e?1:0;if(Array.isArray(e))return e.map(t=>this.normalizeForComparison(t));if(e&&typeof e=="object")try{return JSON.stringify(e)}catch{return String(e)}return e}evalValueExpr(e,t,r,n="t0"){if(r.type==="column"){let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}return this.normalizeForComparison(r.value)}buildFieldCondition(e,t){if("field"in e)return{left:{type:"column",tableAlias:e.alias||t,field:e.field},operator:e.operator||"=",right:{type:"literal",value:e.value},...e.operator==="BETWEEN"?{}:{likeMode:e.likeMode}};if("left"in e)return e;throw new Error("Invalid field condition")}matchFilterExpression(e,t,r,n="t0"){if(_(r))return r.and.every(i=>this.matchFilterExpression(e,t,i,n));if(B(r))return r.or.some(i=>this.matchFilterExpression(e,t,i,n));if(r!==null&&typeof r=="object"&&!Array.isArray(r)){if("left"in r||"field"in r){let i=this.buildFieldCondition(r,n),s=this.evalValueExpr(e,t,i.left,n),a=this.evalValueExpr(e,t,i.right,n);if(i.operator==="IS NULL")return s==null;if(i.operator==="IS NOT NULL")return s!=null;if(i.operator==="IN"||i.operator==="NOT IN"){if(!Array.isArray(a))throw new Error("IN/NOT IN requires array as right value");let c=a.includes(s);return i.operator==="IN"?c:!c}if(i.operator==="BETWEEN")return!Array.isArray(a)||a.length!==2?!1:s>=a[0]&&s<=a[1];switch(i.operator){case"=":return s===a;case"!=":return s!==a;case"<":return s<a;case"<=":return s<=a;case">":return s>a;case">=":return s>=a;case"LIKE":{if(typeof s!="string"||typeof a!="string")return!1;let c=a.replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${c}$`,i.likeMode==="case-insensitive"?"i":"").test(s)}default:throw new Error(`Unsupported operator: ${i.operator}`)}}return Object.entries(r).every(([i,s])=>this.normalizeForComparison(e[i])===this.normalizeForComparison(s))}throw new Error(`Invalid filter expression: ${JSON.stringify(r)}`)}async simulateJoins(e,t){if(t.length===0)return e.map(i=>({record:i,joined:{}}));let r={};for(let i of t)r[i.alias]=await this.listRows(i.table);let n=[];for(let i of e){let s={},a=!0;for(let c of t){let u=r[c.alias]||[],l=null;if(c.conditions.length===1){let b=c.conditions[0];if(b.operator==="="&&b.left.type==="column"&&b.right.type==="column"){let w=b.left.tableAlias||"t0",m=b.right.type==="column"?b.right.tableAlias:void 0;if(w==="t0"&&b.left.type==="column"&&m===c.alias&&b.right.type==="column"){let p=this.normalizeForComparison(i[b.left.field]),g=b.right.field;l=u.find(h=>this.normalizeForComparison(h[g])===p)}}}if(!l&&(c.type==="inner"||!c.type)){a=!1;break}s[c.alias]=l!=null?l:null}a&&n.push({record:i,joined:s})}return n}extractField(e,t,r,n="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}extractAggregateField(e,t,r,n="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let i=r.tableAlias||n,s=i===n?e:t[i]||{};return this.normalizeForComparison(s[r.field])}async getSchemaVersion(){return this.getMetaValue(S,0)}async setSchemaVersion(e){await this.setMetaValue(S,e)}async addTable(e){await this.ensureTable(e.name);let t=await this.getTableSchemas();t[e.name]=T(e),await this.setTableSchemas(t)}async addColumn(e,t){var i;let r=await this.getTableSchemas(),n=(i=r[e])!=null?i:{name:e,columns:[],skipMetadataColumns:!0};n.columns.find(s=>s.name===t.name)||n.columns.push(T(t)),r[e]=n,await this.setTableSchemas(r)}async get(e,t){var i;return await this.tableExists(e)?typeof t=="string"?this.withStore(e,"readonly",async s=>{let a=await f(s.get(t));return a!=null?a:null}):(i=(await this.query(e,{...t,limit:1}))[0])!=null?i:null:null}async query(e,t={}){var l,b;if(!await this.tableExists(e))return[];let n=(l=t.mainAlias)!=null?l:"t0",i=await this.listRows(e),s=await this.simulateJoins(i,t.joins||[]);if(t.filters&&(s=s.filter(({record:w,joined:m})=>this.matchFilterExpression(w,m,t.filters,n))),t.aggregates&&t.aggregates.length>0){let w={};for(let m of t.aggregates){let p=s;m.filter&&(p=p.filter(({record:d,joined:E})=>this.matchFilterExpression(d,E,m.filter,n)));let g=p.map(({record:d,joined:E})=>this.extractAggregateField(d,E,m.field,n)),h=g.map(d=>Number(d)).filter(d=>Number.isFinite(d)),y=null;switch(m.function){case"COUNT":y=g.filter(d=>d!=null).length;break;case"SUM":y=h.reduce((d,E)=>d+E,0);break;case"AVG":y=h.length?h.reduce((d,E)=>d+E,0)/h.length:0;break;case"MIN":y=h.length?Math.min(...h):null;break;case"MAX":y=h.length?Math.max(...h):null;break;default:throw new Error(`Unsupported aggregate function: ${m.function}`)}w[m.as]=y}return[w]}let a=s.map(({record:w,joined:m})=>{if(!t.fields)return w;let p={};for(let[g,h]of Object.entries(t.fields)){let y=g===n?w:m[g]||{};for(let d of h)p[`${g}_${d}`]=y[d]}return p});t.sort&&t.sort.length>0&&a.sort((w,m)=>{for(let p of t.sort){let g=this.extractField(w,{},p.field,n),h=this.extractField(m,{},p.field,n);if(g===h)continue;if(g==null)return p.direction==="asc"?-1:1;if(h==null)return p.direction==="asc"?1:-1;let y=g>h?1:-1;return p.direction==="asc"?y:-y}return 0});let c=(b=t.offset)!=null?b:0,u=t.limit!=null?c+t.limit:void 0;return a.slice(c,u)}async create(e,t){await this.ensureTable(e);let r=t.id;if(!r||typeof r!="string")throw new Error(`create(${e}) requires string id`);return this.withStore(e,"readwrite",async n=>{if(await f(n.get(r)))throw new Error(`Record already exists: ${e}#${r}`);return await f(n.put(T(t))),T(t)})}async update(e,t,r){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);return this.withStore(e,"readwrite",async i=>{let s=await f(i.get(t));if(!s)throw new Error(`Record not found: ${e}#${t}`);let a={...s,...T(r),id:t};return await f(i.put(a)),T(a)})}async delete(e,t){await this.tableExists(e)&&await this.withStore(e,"readwrite",async n=>{await f(n.delete(t))})}async bulkCreate(e,t){await this.ensureTable(e);let n=(await this.getDb()).transaction(e,"readwrite"),i=n.objectStore(e),s=[];for(let a of t){if(!a.id||typeof a.id!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);let c=a.id;if(typeof c!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);if(await f(i.get(c)))throw n.abort(),new Error(`Record already exists: ${e}#${c}`);let l=T(a);await f(i.put(l)),s.push(l)}return await D(n),s}async bulkUpdate(e,t){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);let i=(await this.getDb()).transaction(e,"readwrite"),s=i.objectStore(e),a=[];for(let c of t){let u=await f(s.get(c.id));if(!u)throw i.abort(),new Error(`Record not found: ${e}#${c.id}`);let l={...u,...T(c.updates),id:c.id};await f(s.put(l)),a.push(T(l))}return await D(i),a}async dataStores(){let e=await this.getDb();return Array.from(e.objectStoreNames).filter(t=>t!==x)}async takeSnapshot(){let e=await this.dataStores(),t={};for(let r of e)t[r]=await this.listRows(r);return t}async restoreSnapshot(e){var r;let t=await this.dataStores();for(let n of t){let i=(r=e[n])!=null?r:[];await this.putRows(n,i)}}async transaction(e){let t=this.transactionDepth===0,r=null;t&&(r=await this.takeSnapshot()),this.transactionDepth+=1;try{let n=await e(this);return this.transactionDepth-=1,n}catch(n){throw this.transactionDepth-=1,t&&r&&await this.restoreSnapshot(r),n}}async exportBackupPayload(){let e=await this.getDb(),[t,r,n]=await Promise.all([this.getMetaValue(S,0),this.getTableSchemas(),this.takeSnapshot()]);return{schema:"ofplatform-web-indexeddb-backup-v1",dbName:this.dbName,dbVersion:e.version,exportedAt:new Date().toISOString(),schemaVersion:t,tableSchemas:r,tables:n}}async importBackupPayload(e,t={}){var i,s,a,c;if(e.schema!=="ofplatform-web-indexeddb-backup-v1")throw new Error("Invalid backup payload schema");let r=(i=t.clearExisting)!=null?i:!0,n=Object.keys((s=e.tables)!=null?s:{});for(let u of n)await this.ensureTable(u);if(r){let u=await this.dataStores();for(let l of u)n.includes(l)||await this.putRows(l,[])}for(let u of n){let l=Array.isArray(e.tables[u])?e.tables[u]:[];await this.putRows(u,l)}await this.setMetaValue(S,(a=e.schemaVersion)!=null?a:0),await this.setTableSchemas((c=e.tableSchemas)!=null?c:{})}};var z=!1;var W=!1;var J=!1;var C={platform:"web",ready:!0,notes:"IndexedDbAdapter is available; other adapter domains are scaffold-only."};
1
+ "use strict";var I=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var $=(c,e)=>{for(var t in e)I(c,t,{get:e[t],enumerable:!0})},L=(c,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of j(e))!V.call(c,i)&&i!==t&&I(c,i,{get:()=>e[i],enumerable:!(r=_(e,i))||r.enumerable});return c};var U=c=>L(I({},"__esModule",{value:!0}),c);var X={};$(X,{FetchHttpAdapter:()=>P,IndexedDbAdapter:()=>E,OFPLATFORM_WEB_HTTP_ADAPTERS_READY:()=>q,OFPLATFORM_WEB_PLATFORM_ADAPTERS_READY:()=>K,OFPLATFORM_WEB_SOCKET_ADAPTERS_READY:()=>Y,OFPLATFORM_WEB_STATUS:()=>Z,OFPLATFORM_WEB_STORAGE_ADAPTERS_READY:()=>G,WebPlatformAdapter:()=>D,WebServerAuthoritativeAuthHostSupport:()=>B,WebSocketAdapter:()=>O,WebStorageAdapter:()=>A});module.exports=U(X);var S="__ofcore_meta__",k="__schema_version__",F="__table_schemas__";function T(c){return typeof structuredClone=="function"?structuredClone(c):JSON.parse(JSON.stringify(c))}function H(c){return!!(c&&typeof c=="object"&&!Array.isArray(c)&&"and"in c&&Array.isArray(c.and))}function J(c){return!!(c&&typeof c=="object"&&!Array.isArray(c)&&"or"in c&&Array.isArray(c.or))}function w(c){return new Promise((e,t)=>{c.onsuccess=()=>e(c.result),c.onerror=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB request failed"))}})}function C(c){return new Promise((e,t)=>{c.oncomplete=()=>e(),c.onerror=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB transaction failed"))},c.onabort=()=>{var r;return t((r=c.error)!=null?r:new Error("IndexedDB transaction aborted"))}})}var E=class{constructor(e={}){this.transactionDepth=0;var t,r;this.dbName=(t=e.dbName)!=null?t:"ofcore-app",this.initialDbVersion=e.initialDbVersion,this.tableNamePrefix=(r=e.tableNamePrefix)!=null?r:"",this.tableNameResolver=e.tableNameMapper}setTableNameResolver(e){this.tableNameResolver=e}async close(){this.closeSync();let e=this.dbPromise;if(this.dbPromise=void 0,!!e)try{(await e).close()}catch{}}closeSync(){var e;try{(e=this.dbInstance)==null||e.close()}catch{}this.dbInstance=void 0,this.dbPromise=void 0}resolveTableName(e){return e===S?S:this.tableNameResolver?String(this.tableNameResolver(e)||"").trim()||e:!this.tableNamePrefix||e.startsWith(this.tableNamePrefix)?e:`${this.tableNamePrefix}${e}`}async openDatabase(e){let t=typeof e=="number"?indexedDB.open(this.dbName,e):indexedDB.open(this.dbName);return t.onupgradeneeded=()=>{let r=t.result;r.objectStoreNames.contains(S)||r.createObjectStore(S,{keyPath:"key"})},new Promise((r,i)=>{t.onsuccess=()=>{let n=t.result;n.onversionchange=()=>n.close(),this.dbInstance=n,r(n)},t.onerror=()=>{let n=t.error;if((n==null?void 0:n.name)==="VersionError"){i(new Error(`IndexedDB version mismatch for "${this.dbName}": requested version is lower than existing database version`));return}i(n!=null?n:new Error("Failed to open IndexedDB"))},t.onblocked=()=>i(new Error("IndexedDB open blocked by another tab/process"))})}async getDb(){return this.dbPromise||(this.dbPromise=(async()=>{try{return await this.openDatabase(this.initialDbVersion)}catch(e){if(this.initialDbVersion!==void 0&&e instanceof Error&&e.message.includes("version mismatch"))return this.openDatabase();throw e}})()),this.dbPromise}async reopenWithUpgrade(e){let t=await this.getDb(),r=t.version+1;t.close(),this.dbPromise=void 0;let i=indexedDB.open(this.dbName,r);i.onupgradeneeded=()=>{let s=i.result;s.objectStoreNames.contains(S)||s.createObjectStore(S,{keyPath:"key"}),e(s)};let n=await new Promise((s,o)=>{i.onsuccess=()=>{let l=i.result;l.onversionchange=()=>l.close(),this.dbInstance=l,s(l)},i.onerror=()=>{var l;return o((l=i.error)!=null?l:new Error("Failed to upgrade IndexedDB"))},i.onblocked=()=>o(new Error("IndexedDB upgrade blocked by another tab/process"))});return this.dbPromise=Promise.resolve(n),n}async withStore(e,t,r){let n=(await this.getDb()).transaction(e,t),s=n.objectStore(e),o=await r(s);return await C(n),o}async getMetaValue(e,t){return(await this.getDb()).objectStoreNames.contains(S)?this.withStore(S,"readonly",async i=>{var s;let n=await w(i.get(e));return(s=n==null?void 0:n.value)!=null?s:t}):t}async setMetaValue(e,t){await this.withStore(S,"readwrite",async r=>{await w(r.put({key:e,value:t}))})}async getTableSchemas(){return this.getMetaValue(F,{})}async setTableSchemas(e){await this.setMetaValue(F,e)}async tableExists(e){let t=this.resolveTableName(e);return(await this.getDb()).objectStoreNames.contains(t)}async ensureTable(e){let t=this.resolveTableName(e);await this.tableExists(e)||await this.reopenWithUpgrade(r=>{r.objectStoreNames.contains(t)||r.createObjectStore(t,{keyPath:"id"})})}async listRows(e){return await this.tableExists(e)?this.withStore(this.resolveTableName(e),"readonly",async r=>w(r.getAll())):[]}async putRows(e,t){await this.ensureTable(e);let r=this.resolveTableName(e),n=(await this.getDb()).transaction(r,"readwrite"),s=n.objectStore(r);await w(s.clear());for(let o of t)await w(s.put(o));await C(n)}normalizeForComparison(e){if(e instanceof Date)return e.getTime();if(typeof e=="boolean")return e?1:0;if(Array.isArray(e))return e.map(t=>this.normalizeForComparison(t));if(e&&typeof e=="object")try{return JSON.stringify(e)}catch{return String(e)}return e}evalValueExpr(e,t,r,i="t0"){if(r.type==="column"){let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}return this.normalizeForComparison(r.value)}buildFieldCondition(e,t){if("field"in e)return{left:{type:"column",tableAlias:e.alias||t,field:e.field},operator:e.operator||"=",right:{type:"literal",value:e.value},...e.operator==="BETWEEN"?{}:{likeMode:e.likeMode}};if("left"in e)return e;throw new Error("Invalid field condition")}matchFilterExpression(e,t,r,i="t0"){if(H(r))return r.and.every(n=>this.matchFilterExpression(e,t,n,i));if(J(r))return r.or.some(n=>this.matchFilterExpression(e,t,n,i));if(r!==null&&typeof r=="object"&&!Array.isArray(r)){if("left"in r||"field"in r){let n=this.buildFieldCondition(r,i),s=this.evalValueExpr(e,t,n.left,i),o=this.evalValueExpr(e,t,n.right,i);if(n.operator==="IS NULL")return s==null;if(n.operator==="IS NOT NULL")return s!=null;if(n.operator==="IN"||n.operator==="NOT IN"){if(!Array.isArray(o))throw new Error("IN/NOT IN requires array as right value");let l=o.includes(s);return n.operator==="IN"?l:!l}if(n.operator==="BETWEEN")return!Array.isArray(o)||o.length!==2?!1:s>=o[0]&&s<=o[1];switch(n.operator){case"=":return s===o;case"!=":return s!==o;case"<":return s<o;case"<=":return s<=o;case">":return s>o;case">=":return s>=o;case"LIKE":{if(typeof s!="string"||typeof o!="string")return!1;let l=o.replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${l}$`,n.likeMode==="case-insensitive"?"i":"").test(s)}default:throw new Error(`Unsupported operator: ${n.operator}`)}}return Object.entries(r).every(([n,s])=>this.normalizeForComparison(e[n])===this.normalizeForComparison(s))}throw new Error(`Invalid filter expression: ${JSON.stringify(r)}`)}async simulateJoins(e,t){if(t.length===0)return e.map(n=>({record:n,joined:{}}));let r={};for(let n of t)r[n.alias]=await this.listRows(n.table);let i=[];for(let n of e){let s={},o=!0;for(let l of t){let a=r[l.alias]||[],d=null;if(l.conditions.length===1){let h=l.conditions[0];if(h.operator==="="&&h.left.type==="column"&&h.right.type==="column"){let u=h.left.tableAlias||"t0",p=h.right.type==="column"?h.right.tableAlias:void 0;if(u==="t0"&&h.left.type==="column"&&p===l.alias&&h.right.type==="column"){let g=this.normalizeForComparison(n[h.left.field]),b=h.right.field;d=a.find(f=>this.normalizeForComparison(f[b])===g)}}}if(!d&&(l.type==="inner"||!l.type)){o=!1;break}s[l.alias]=d!=null?d:null}o&&i.push({record:n,joined:s})}return i}extractField(e,t,r,i="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}extractAggregateField(e,t,r,i="t0"){if(typeof r=="string")return this.normalizeForComparison(e[r]);let n=r.tableAlias||i,s=n===i?e:t[n]||{};return this.normalizeForComparison(s[r.field])}async getSchemaVersion(){return this.getMetaValue(k,0)}async setSchemaVersion(e){await this.setMetaValue(k,e)}async addTable(e){await this.ensureTable(e.name);let t=await this.getTableSchemas();t[e.name]=T(e),await this.setTableSchemas(t)}async addColumn(e,t){var n;let r=await this.getTableSchemas(),i=(n=r[e])!=null?n:{name:e,columns:[],skipMetadataColumns:!0};i.columns.find(s=>s.name===t.name)||i.columns.push(T(t)),r[e]=i,await this.setTableSchemas(r)}async get(e,t){var n;return await this.tableExists(e)?typeof t=="string"?this.withStore(this.resolveTableName(e),"readonly",async s=>{let o=await w(s.get(t));return o!=null?o:null}):(n=(await this.query(e,{...t,limit:1}))[0])!=null?n:null:null}async query(e,t={}){var d,h;if(!await this.tableExists(e))return[];let i=(d=t.mainAlias)!=null?d:"t0",n=await this.listRows(e),s=await this.simulateJoins(n,t.joins||[]);if(t.filters&&(s=s.filter(({record:u,joined:p})=>this.matchFilterExpression(u,p,t.filters,i))),t.aggregates&&t.aggregates.length>0){let u={};for(let p of t.aggregates){let g=s;p.filter&&(g=g.filter(({record:m,joined:v})=>this.matchFilterExpression(m,v,p.filter,i)));let b=g.map(({record:m,joined:v})=>this.extractAggregateField(m,v,p.field,i)),f=b.map(m=>Number(m)).filter(m=>Number.isFinite(m)),y=null;switch(p.function){case"COUNT":y=b.filter(m=>m!=null).length;break;case"SUM":y=f.reduce((m,v)=>m+v,0);break;case"AVG":y=f.length?f.reduce((m,v)=>m+v,0)/f.length:0;break;case"MIN":y=f.length?Math.min(...f):null;break;case"MAX":y=f.length?Math.max(...f):null;break;default:throw new Error(`Unsupported aggregate function: ${p.function}`)}u[p.as]=y}return[u]}t.sort&&t.sort.length>0&&s.sort((u,p)=>{for(let g of t.sort){let b=this.extractField(u.record,u.joined,g.field,i),f=this.extractField(p.record,p.joined,g.field,i);if(b===f)continue;if(b==null)return g.direction==="asc"?-1:1;if(f==null)return g.direction==="asc"?1:-1;let y=b>f?1:-1;return g.direction==="asc"?y:-y}return 0});let o=s.map(({record:u,joined:p})=>{var f;if(!t.fields)return u;if(((f=t.resultShape)!=null?f:"flat")==="nested"){let y={};for(let[m,v]of Object.entries(t.fields)){let R=m===i?u:p[m]||{},N={};for(let M of v)N[M]=R[M];y[m]=N}return y}let b={};for(let[y,m]of Object.entries(t.fields)){let v=y===i?u:p[y]||{};for(let R of m)b[`${y}_${R}`]=v[R]}return b}),l=(h=t.offset)!=null?h:0,a=t.limit!=null?l+t.limit:void 0;return o.slice(l,a)}async create(e,t){await this.ensureTable(e);let r=t.id;if(!r||typeof r!="string")throw new Error(`create(${e}) requires string id`);return this.withStore(this.resolveTableName(e),"readwrite",async i=>{if(await w(i.get(r)))throw new Error(`Record already exists: ${e}#${r}`);return await w(i.put(T(t))),T(t)})}async update(e,t,r){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);return this.withStore(this.resolveTableName(e),"readwrite",async n=>{let s=await w(n.get(t));if(!s)throw new Error(`Record not found: ${e}#${t}`);let o={...s,...T(r),id:t};return await w(n.put(o)),T(o)})}async delete(e,t){await this.tableExists(e)&&await this.withStore(this.resolveTableName(e),"readwrite",async i=>{await w(i.delete(t))})}async bulkCreate(e,t){await this.ensureTable(e);let r=this.resolveTableName(e),n=(await this.getDb()).transaction(r,"readwrite"),s=n.objectStore(r),o=[];for(let l of t){if(!l.id||typeof l.id!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);let a=l.id;if(typeof a!="string")throw n.abort(),new Error(`bulkCreate(${e}) requires string id per row`);if(await w(s.get(a)))throw n.abort(),new Error(`Record already exists: ${e}#${a}`);let h=T(l);await w(s.put(h)),o.push(h)}return await C(n),o}async bulkUpdate(e,t){if(!await this.tableExists(e))throw new Error(`Table not found: ${e}`);let i=this.resolveTableName(e),s=(await this.getDb()).transaction(i,"readwrite"),o=s.objectStore(i),l=[];for(let a of t){let d=await w(o.get(a.id));if(!d)throw s.abort(),new Error(`Record not found: ${e}#${a.id}`);let h={...d,...T(a.updates),id:a.id};await w(o.put(h)),l.push(T(h))}return await C(s),l}async dataStores(){let e=await this.getDb();return Array.from(e.objectStoreNames).filter(t=>t!==S)}async takeSnapshot(){let e=await this.dataStores(),t={};for(let r of e)t[r]=await this.listRows(r);return t}async restoreSnapshot(e){var r;let t=await this.dataStores();for(let i of t){let n=(r=e[i])!=null?r:[];await this.putRows(i,n)}}async transaction(e){let t=this.transactionDepth===0,r=null;t&&(r=await this.takeSnapshot()),this.transactionDepth+=1;try{let i=await e(this);return this.transactionDepth-=1,i}catch(i){throw this.transactionDepth-=1,t&&r&&await this.restoreSnapshot(r),i}}async exportBackupPayload(){let e=await this.getDb(),[t,r,i]=await Promise.all([this.getMetaValue(k,0),this.getTableSchemas(),this.takeSnapshot()]);return{schema:"ofplatform-web-indexeddb-backup-v1",dbName:this.dbName,dbVersion:e.version,exportedAt:new Date().toISOString(),schemaVersion:t,tableSchemas:r,tables:i}}async importBackupPayload(e,t={}){var n,s,o,l;if(e.schema!=="ofplatform-web-indexeddb-backup-v1")throw new Error("Invalid backup payload schema");let r=(n=t.clearExisting)!=null?n:!0,i=Object.keys((s=e.tables)!=null?s:{});for(let a of i)await this.ensureTable(a);if(r){let a=await this.dataStores();for(let d of a)i.includes(d)||await this.putRows(d,[])}for(let a of i){let d=Array.isArray(e.tables[a])?e.tables[a]:[];await this.putRows(a,d)}await this.setMetaValue(k,(o=e.schemaVersion)!=null?o:0),await this.setTableSchemas((l=e.tableSchemas)!=null?l:{})}};function z(c,e){if(!e||Object.keys(e).length===0)return c;let t=new URLSearchParams;for(let[i,n]of Object.entries(e))n!=null&&t.set(i,String(n));let r=t.toString();return r?c.includes("?")?`${c}&${r}`:`${c}?${r}`:c}var P=class{constructor(e={}){let t=e.fetch;if(t)this.fetchImpl=t;else{let r=globalThis.fetch;if(typeof r!="function")throw new Error("FetchHttpAdapter: globalThis.fetch is not available. Pass a fetch implementation via options.fetch.");this.fetchImpl=r.bind(globalThis)}}async request(e){var n,s;let t=z(e.url,e.query),r=typeof AbortController!="undefined"?new AbortController:null,i=r&&e.timeoutMs?setTimeout(()=>r.abort(),e.timeoutMs):null;try{let o=e.body!==void 0&&e.body!==null,l={...(n=e.headers)!=null?n:{}};o&&!l["content-type"]&&!l["Content-Type"]&&(l["content-type"]="application/json");let a=await this.fetchImpl(t,{method:e.method,headers:l,body:o?JSON.stringify(e.body):void 0,signal:r==null?void 0:r.signal}),d={};a.headers.forEach((p,g)=>{d[g.toLowerCase()]=p});let u=((s=d["content-type"])!=null?s:"").includes("application/json")?await a.json():await a.text();return{status:a.status,headers:d,data:u}}finally{i!==null&&clearTimeout(i)}}};var q=!0;var D=class{constructor(e={}){var t;if(this.env=(t=e.env)!=null?t:{},e.crypto)this.cryptoImpl=e.crypto;else{let r=globalThis.crypto;if(!(r!=null&&r.subtle))throw new Error("WebPlatformAdapter: globalThis.crypto.subtle is not available. Pass a crypto implementation via options.crypto.");this.cryptoImpl=r}}getEnvVar(e){return this.env[e]}isOnline(){let e=globalThis.navigator;return e&&typeof e.onLine!="undefined"?!!e.onLine:!0}async hashPin(e){return`sha256:${await this.sha256Hex(e)}`}async verifyPin(e,t){if(!t.startsWith("sha256:"))return!1;let r=t.slice(7);return/^[0-9a-f]{64}$/.test(r)?await this.sha256Hex(e)===r:r===e}async sha256Hex(e){let r=new TextEncoder().encode(e),i=await this.cryptoImpl.subtle.digest("SHA-256",r);return Array.from(new Uint8Array(i)).map(n=>n.toString(16).padStart(2,"0")).join("")}};var K=!0;function Q(c){let e=c.query;if(!e||Object.keys(e).length===0)return c.url;let r=new URLSearchParams(e).toString();return c.url.includes("?")?`${c.url}&${r}`:`${c.url}?${r}`}var O=class{constructor(e={}){this.ws=null;this.connectionOptions=null;this.listeners=new Map;this.activeConnectionId=0;this.reconnectAttempts=0;this.reconnectTimeout=null;this.intentionalClose=!1;var r,i,n,s,o;this.reconnect=(r=e.reconnect)!=null?r:!0,this.maxReconnectAttempts=Math.max(0,(i=e.maxReconnectAttempts)!=null?i:5),this.reconnectDelayMs=Math.max(0,(n=e.reconnectDelayMs)!=null?n:500),this.maxReconnectDelayMs=Math.max(this.reconnectDelayMs,(s=e.maxReconnectDelayMs)!=null?s:3e4);let t=(o=e.webSocket)!=null?o:globalThis.WebSocket;if(typeof t!="function")throw new Error("WebSocketAdapter: globalThis.WebSocket is not available. Pass a WebSocket constructor via options.webSocket.");this.WebSocketCtor=t}connect(e){if(this.clearReconnectTimeout(),this.intentionalClose=!0,this.ws)try{this.ws.close(1e3,"client reconnect")}catch{}finally{this.ws=null}this.connectionOptions=e,this.intentionalClose=!1,this.reconnectAttempts=0;let t=++this.activeConnectionId;return this.openConnection(e,t)}async disconnect(){this.intentionalClose=!0,this.clearReconnectTimeout(),this.activeConnectionId+=1,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}subscribe(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{let r=this.listeners.get(e);r&&(r.delete(t),r.size===0&&this.listeners.delete(e))}}async publish(e,t){var i,n;if(!this.ws||this.ws.readyState!==this.ws.OPEN)throw new Error(`WebSocketAdapter: cannot publish "${e}" \u2014 not connected (readyState=${(n=(i=this.ws)==null?void 0:i.readyState)!=null?n:"null"}).`);let r={event:e,payload:t};this.ws.send(JSON.stringify(r))}isConnected(){return this.ws!==null&&this.ws.readyState===this.ws.OPEN}openConnection(e,t){return new Promise((r,i)=>{let n=Q(e),s;try{s=new this.WebSocketCtor(n)}catch(o){i(o instanceof Error?o:new Error(String(o)));return}s.onopen=()=>{if(t!==this.activeConnectionId){try{s.close(1e3,"stale connection")}catch{}r();return}this.ws=s,this.reconnectAttempts=0,r()},s.onerror=o=>{t===this.activeConnectionId&&(this.ws||i(new Error("WebSocketAdapter: connection failed")))},s.onclose=o=>{if(t!==this.activeConnectionId)return;this.ws===s&&(this.ws=null),!this.intentionalClose&&this.reconnect&&this.connectionOptions&&this.reconnectAttempts<this.maxReconnectAttempts&&this.scheduleReconnect()},s.onmessage=o=>{this.handleMessage(o.data)}})}handleMessage(e){if(typeof e!="string")return;let t;try{t=JSON.parse(e)}catch{return}if(typeof t!="object"||t===null||typeof t.event!="string")return;let{event:r,payload:i}=t,n=this.listeners.get(r);if(n)for(let s of n)try{s(i)}catch{}}scheduleReconnect(){this.clearReconnectTimeout();let e=++this.reconnectAttempts,t=Math.min(this.reconnectDelayMs*Math.pow(2,e-1),this.maxReconnectDelayMs);this.reconnectTimeout=setTimeout(()=>{if(!this.intentionalClose&&this.connectionOptions){let r=this.activeConnectionId;this.openConnection(this.connectionOptions,r).catch(()=>{})}},t)}clearReconnectTimeout(){this.reconnectTimeout!==null&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null)}};var Y=!0;var A=class{constructor(e={}){if(e.storage)this.store=e.storage;else{let t=globalThis.localStorage;if(!t)throw new Error("WebStorageAdapter: globalThis.localStorage is not available. Pass a storage implementation via options.storage.");this.store=t}}getItem(e){return this.store.getItem(e)}setItem(e,t){this.store.setItem(e,t)}removeItem(e){this.store.removeItem(e)}};var G=!0;var x=require("ofauth-shared-core");function W(c,e){var i,n,s,o,l;let t=new Date().toISOString(),r=c&&typeof c=="object"?c:{};return{sessionId:String((i=r.sessionId)!=null?i:""),identityId:String((n=r.identityId)!=null?n:""),assuranceLevel:String((s=r.assuranceLevel)!=null?s:"basic"),issuedAt:String((o=r.issuedAt)!=null?o:t),expiresAt:String((l=r.expiresAt)!=null?l:e)}}var B=class{constructor(e){this.options=e;var i,n,s,o,l;if(this.serverBaseUrl=((i=e.serverBaseUrl)!=null?i:"").trim().replace(/\/+$/,""),this.cacheKey=e.cacheKey.trim(),this.targetTenantId=((n=e.targetTenantId)==null?void 0:n.trim())||"",this.targetBranchId=((s=e.targetBranchId)==null?void 0:s.trim())||"",this.targetDomainId=((o=e.targetDomainId)==null?void 0:o.trim())||"",this.storage=new A(e.storage?{storage:e.storage}:{}),e.fetchImpl)this.fetchImpl=e.fetchImpl;else if(typeof globalThis.fetch=="function")this.fetchImpl=globalThis.fetch.bind(globalThis);else throw new Error("WebServerAuthoritativeAuthHostSupport requires a fetch implementation");if(this.isOnlineResolver=(l=e.isOnline)!=null?l:(()=>typeof navigator=="undefined"||typeof navigator.onLine=="undefined"?!0:!!navigator.onLine),!this.serverBaseUrl){this.serverBridge=null;return}let t={login:async({principal:a,secret:d})=>{var g,b,f,y;let h=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({principal:a,secret:d,...this.targetTenantId?{targetTenantId:this.targetTenantId}:{},...this.targetBranchId?{targetBranchId:this.targetBranchId}:{},...this.targetDomainId?{targetDomainId:this.targetDomainId}:{}})}),u=await h.json().catch(()=>({}));if(!h.ok)throw new x.AuthRejectionError(String((g=u.code)!=null?g:"AUTH_UNAUTHORIZED"));let p=new Date(Date.now()+15*6e4).toISOString();return{accessToken:String((b=u.accessToken)!=null?b:""),refreshToken:String((f=u.refreshToken)!=null?f:""),session:W(u.session,p),activeContext:(y=u.activeContext)!=null?y:null,activeRoleRef:u.activeRoleRef==null?"":String(u.activeRoleRef)}},refresh:async({refreshToken:a})=>{var p,g,b,f;let d=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})}),h=await d.json().catch(()=>({}));if(!d.ok)throw new x.AuthRejectionError(String((p=h.code)!=null?p:"AUTH_UNAUTHORIZED"));let u=new Date(Date.now()+15*6e4).toISOString();return{accessToken:String((g=h.accessToken)!=null?g:""),refreshToken:String((b=h.refreshToken)!=null?b:""),session:W(h.session,u),activeContext:(f=h.activeContext)!=null?f:null}},logout:async({accessToken:a,refreshToken:d})=>{let h={"Content-Type":"application/json"};a&&(h.Authorization=`Bearer ${a}`),await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/logout`,{method:"POST",headers:h,body:JSON.stringify({refreshToken:d})}).catch(()=>{})}},r={getCachedIdentity:async a=>{var d,h;return(h=(d=this.readCacheRecord(a))==null?void 0:d.identity)!=null?h:null},setCachedIdentity:async a=>{let d=this.readCacheRecord(a.principal);this.writeCacheRecord(a.principal,{...d,identity:a})},clearCachedIdentity:async a=>{let d=this.readCacheRecord(a);d&&this.writeCacheRecord(a,{...d,identity:void 0})},getSessionEnvelope:async a=>{var d,h;return(h=(d=this.readCacheRecord(a))==null?void 0:d.envelope)!=null?h:null},setSessionEnvelope:async(a,d)=>{let h=this.readCacheRecord(a);this.writeCacheRecord(a,{...h,envelope:d})},clearSessionEnvelope:async a=>{let d=this.readCacheRecord(a);d&&this.writeCacheRecord(a,{...d,envelope:void 0})}};this.serverBridge=(0,x.createServerAuthoritativeAuthBridge)({authority:t,cache:r,isOnline:()=>this.isOnlineResolver(),verifyOfflineSecret:this.options.verifyOfflineSecret})}get isServerConfigured(){return this.serverBaseUrl.length>0}loadCacheMap(){try{let e=this.storage.getItem(this.cacheKey);if(!e)return{};let t=JSON.parse(e);return t&&typeof t=="object"?t:{}}catch{return{}}}saveCacheMap(e){try{this.storage.setItem(this.cacheKey,JSON.stringify(e))}catch{}}readCacheRecord(e){var r;return(r=this.loadCacheMap()[e])!=null?r:null}writeCacheRecord(e,t){let r=this.loadCacheMap(),i={};t.identity&&(i.identity=t.identity),t.envelope&&(i.envelope=t.envelope),!i.identity&&!i.envelope?delete r[e]:r[e]=i,this.saveCacheMap(r)}clearCachedSessionEnvelope(e){let t=this.readCacheRecord(e);t&&this.writeCacheRecord(e,{...t,envelope:void 0})}clearAllCachedSessionEnvelopes(){let e=this.loadCacheMap();for(let t of Object.keys(e)){let r=e[t];r&&(e[t]=r.identity?{identity:r.identity}:{},e[t].identity||delete e[t])}this.saveCacheMap(e)}async login(e,t){var i,n;if(!this.serverBridge)return{status:"rejected",reasonCode:"AUTH_BRIDGE_NOT_CONFIGURED"};let r=await this.serverBridge.login({principal:e,secret:t,verifyMethod:"pin"});return r.status==="authenticated"?{status:"authenticated",mode:(i=r.mode)!=null?i:"online"}:{status:"rejected",reasonCode:(n=r.reasonCode)!=null?n:"AUTH_UNAUTHORIZED"}}async logout(e){this.serverBridge&&await this.serverBridge.logout(e)}async restoreCachedEnvelope(){var t,r,i;let e=this.loadCacheMap();for(let n of Object.keys(e)){let s=(t=e[n])==null?void 0:t.envelope;if(!s)continue;if(this.isOnlineResolver()&&s.refreshToken&&this.serverBaseUrl)try{let l=await this.fetchImpl(`${this.serverBaseUrl}/v1/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:s.refreshToken})}),a=await l.json().catch(()=>({}));if(l.ok&&a.accessToken){let d={...s,accessToken:String(a.accessToken),refreshToken:a.refreshToken?String(a.refreshToken):s.refreshToken,activeContext:(i=(r=a.activeContext)!=null?r:s.activeContext)!=null?i:null};return this.writeCacheRecord(n,{...e[n],envelope:d}),{principal:n,envelope:d}}}catch{}let o=Date.parse(s.expiresAt);if(Number.isFinite(o)&&o>Date.now())return{principal:n,envelope:s}}return null}getStoredAccessToken(e){var t,r,i;return(i=(r=(t=this.readCacheRecord(e))==null?void 0:t.envelope)==null?void 0:r.accessToken)!=null?i:""}getStoredEnvelope(e){var t,r;return(r=(t=this.readCacheRecord(e))==null?void 0:t.envelope)!=null?r:null}};var Z={platform:"web",ready:!0,notes:"All adapter domains implemented: IndexedDbAdapter (db), FetchHttpAdapter (http), WebPlatformAdapter (platform), WebSocketAdapter (socket), WebStorageAdapter (storage)."};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofplatform-web",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.2",
4
4
  "private": false,
5
5
  "description": "Web platform adapters for browser-based of* host apps (IndexedDB, browser transport, and future web primitives).",
6
6
  "author": {
@@ -24,17 +24,23 @@
24
24
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
25
25
  "build": "npm run clean && tsc -p tsconfig.build.json && node esbuild.config.js",
26
26
  "typecheck": "tsc -p tsconfig.json --noEmit",
27
- "ci:check": "npm run typecheck && npm run build",
27
+ "test": "npm run build && node --test ./tests/*.test.js",
28
+ "ci:check": "npm run typecheck && npm run test",
28
29
  "prepublishOnly": "npm run ci:check",
29
30
  "prepack": "npm run build",
30
31
  "bundle": "node esbuild.config.js"
31
32
  },
32
33
  "dependencies": {
33
- "ofcore": "0.1.0-alpha.0"
34
+ "ofcore": "0.2.0-alpha.0"
35
+ },
36
+ "peerDependencies": {
37
+ "ofauth-shared-core": "0.2.0-alpha.0"
34
38
  },
35
39
  "devDependencies": {
36
- "typescript": "^5.9.3",
37
- "esbuild": "^0.25.11"
40
+ "esbuild": "^0.25.11",
41
+ "fake-indexeddb": "^6.2.5",
42
+ "ofauth-shared-core": "0.2.0-alpha.0",
43
+ "typescript": "^5.9.3"
38
44
  },
39
45
  "publishConfig": {
40
46
  "access": "public"