ofplatform-electron 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # ofplatform-electron
2
+
3
+ Electron platform adapter package for `of*` ecosystem.
4
+
5
+ ## Structure
6
+
7
+ - `src/adapters/db`:
8
+ - `SQLiteAdapter` (implements `DbAdapter` from `ofcore`)
9
+ - `SQLiteExecutor`
10
+ - `SQLiteQueryBuilder`
11
+ - `src/adapters/platform`:
12
+ - `ElectronPlatformAdapter` (implements `PlatformAdapter`)
13
+ - `src/adapters/storage`:
14
+ - `ElectronStorageAdapter` (file-backed `StorageAdapter`)
15
+ - `src/adapters/desktop`:
16
+ - `ElectronNotificationAdapter`
17
+ - `ElectronExportAdapter`
18
+ - `src/adapters/http`: scaffold placeholder
19
+ - `src/adapters/socket`: scaffold placeholder
20
+
21
+ Planned adapters:
22
+ - Electron SQLCipher hardening profile (follow-up from current SQLite adapter)
23
+ - Optional IPC-backed `HttpAdapter`/`SocketAdapter`
24
+
25
+ Design rule:
26
+ - Keep platform-specific implementation in platform packages, not in `ofcore`.
27
+
28
+ ## Build and Publish
29
+ - `npm run typecheck`
30
+ - `npm run build`
31
+ - `npm run ci:check`
32
+ - `npm pack --dry-run`
33
+ - `npm publish --access public`
@@ -0,0 +1,25 @@
1
+ import type { ColumnDefinition, DbAdapter, LiteralValue, QueryOptions, TableSchema } from 'ofcore';
2
+ export interface SQLiteAdapterOptions {
3
+ dbPath: string;
4
+ }
5
+ export declare class SQLiteAdapter implements DbAdapter {
6
+ private readonly executor;
7
+ private savepointDepth;
8
+ private readonly schemaMap;
9
+ constructor(options: SQLiteAdapterOptions);
10
+ getSchemaVersion(): Promise<number>;
11
+ setSchemaVersion(version: number): Promise<void>;
12
+ addTable(table: TableSchema): Promise<void>;
13
+ addColumn(table: string, column: ColumnDefinition): Promise<void>;
14
+ get<T>(table: string, idOrOptions: string | QueryOptions): Promise<T | null>;
15
+ query<T>(table: string, options?: QueryOptions): Promise<T[]>;
16
+ create<T>(table: string, data: Partial<Record<string, LiteralValue>>): Promise<T>;
17
+ update<T>(table: string, id: string, updates: Partial<Record<string, LiteralValue>>): Promise<T>;
18
+ delete(table: string, id: string): Promise<void>;
19
+ bulkCreate<T>(table: string, rows: Array<Partial<Record<string, LiteralValue>>>): Promise<T[]>;
20
+ bulkUpdate<T>(table: string, rows: Array<{
21
+ id: string;
22
+ updates: Partial<Record<string, LiteralValue>>;
23
+ }>): Promise<T[]>;
24
+ transaction<T>(callback: (tx: this) => Promise<T>): Promise<T>;
25
+ }
@@ -0,0 +1,22 @@
1
+ import Database from 'better-sqlite3';
2
+ import type { ColumnDefinition, FilterExpression, GroupBySpec, QueryOptions, SortSpec } from 'ofcore';
3
+ export declare class SQLiteExecutor {
4
+ private readonly db;
5
+ constructor(dbPath: string);
6
+ initMetaTable(): void;
7
+ run(sql: string, params?: any[]): Database.RunResult;
8
+ get<T = any>(sql: string, params?: any[]): T | undefined;
9
+ all<T = any>(sql: string, params?: any[]): T[];
10
+ transaction<T>(fn: () => T): T;
11
+ escapeIdentifier(id: string): string;
12
+ mapType(type: string): string;
13
+ parseRow<T>(row: any, columnMap: Record<string, ColumnDefinition>): T;
14
+ denormalizeRow<T = Record<string, unknown>>(row: any, mainAlias?: string): T;
15
+ buildFilter(expr: FilterExpression, mainAlias?: string): import("./SQLiteQueryBuilder").BuiltQuery;
16
+ buildGroupBy(groupBy?: GroupBySpec[], mainAlias?: string): string;
17
+ buildSort(sorts?: SortSpec[], mainAlias?: string): string;
18
+ buildSelect(table: string, options?: QueryOptions): import("./SQLiteQueryBuilder").BuiltQuery;
19
+ buildInsert(table: string, data: Record<string, any>): import("./SQLiteQueryBuilder").BuiltQuery;
20
+ buildUpdate(table: string, id: string, updates: Record<string, any>): import("./SQLiteQueryBuilder").BuiltQuery;
21
+ buildDelete(table: string, id: string): import("./SQLiteQueryBuilder").BuiltQuery;
22
+ }
@@ -0,0 +1,18 @@
1
+ import type { FilterExpression, GroupBySpec, LiteralValue, QueryOptions, SortSpec } from 'ofcore';
2
+ export interface BuiltQuery {
3
+ sql: string;
4
+ params: any[];
5
+ }
6
+ export declare class SQLiteQueryBuilder {
7
+ private static escapeIdentifier;
8
+ private static normalizeValue;
9
+ private static buildValueExpr;
10
+ private static buildFieldCondition;
11
+ static buildFilter(expr: FilterExpression, mainAlias?: string): BuiltQuery;
12
+ static buildGroupBy(groupBy?: GroupBySpec[], mainAlias?: string): string;
13
+ static buildSort(sorts?: SortSpec[], mainAlias?: string): string;
14
+ static buildSelect(table: string, options?: QueryOptions): BuiltQuery;
15
+ static buildInsert(table: string, data: Record<string, LiteralValue>): BuiltQuery;
16
+ static buildUpdate(table: string, id: string, updates: Record<string, LiteralValue>): BuiltQuery;
17
+ static buildDelete(table: string, id: string): BuiltQuery;
18
+ }
@@ -0,0 +1,3 @@
1
+ export * from './SQLiteAdapter';
2
+ export * from './SQLiteExecutor';
3
+ export * from './SQLiteQueryBuilder';
@@ -0,0 +1,12 @@
1
+ import type { ExportAdapter } from 'ofcore';
2
+ export interface ElectronExportAdapterOptions {
3
+ documentsPath: string;
4
+ revealFile?: (filePath: string) => Promise<void> | void;
5
+ }
6
+ export declare class ElectronExportAdapter implements ExportAdapter {
7
+ private readonly options;
8
+ constructor(options: ElectronExportAdapterOptions);
9
+ getDocumentPath(fileName: string): string;
10
+ writeFile(filePath: string, content: string): Promise<void>;
11
+ share(filePath: string): Promise<void>;
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { NotificationAdapter, NotificationOptions } from 'ofcore';
2
+ export interface ElectronNotificationAdapterOptions {
3
+ notify: (options: NotificationOptions) => void;
4
+ }
5
+ export declare class ElectronNotificationAdapter implements NotificationAdapter {
6
+ private readonly options;
7
+ constructor(options: ElectronNotificationAdapterOptions);
8
+ configure(_options: unknown): void;
9
+ createChannel(_options: {
10
+ channelId: string;
11
+ channelName: string;
12
+ importance?: number;
13
+ }): void;
14
+ localNotification(options: NotificationOptions): void;
15
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ElectronNotificationAdapter';
2
+ export * from './ElectronExportAdapter';
@@ -0,0 +1 @@
1
+ export declare const OFPLATFORM_ELECTRON_HTTP_ADAPTERS_READY = false;
@@ -0,0 +1,7 @@
1
+ import type { PlatformAdapter } from 'ofcore';
2
+ export declare class ElectronPlatformAdapter implements PlatformAdapter {
3
+ getEnvVar(key: string): string | undefined;
4
+ isOnline(): boolean;
5
+ hashPin(pin: string): Promise<string>;
6
+ verifyPin(pin: string, hashedPin: string): Promise<boolean>;
7
+ }
@@ -0,0 +1 @@
1
+ export * from './ElectronPlatformAdapter';
@@ -0,0 +1 @@
1
+ export declare const OFPLATFORM_ELECTRON_SOCKET_ADAPTERS_READY = false;
@@ -0,0 +1,13 @@
1
+ import type { StorageAdapter } from 'ofcore';
2
+ export interface ElectronStorageAdapterOptions {
3
+ storageDir: string;
4
+ fileName?: string;
5
+ }
6
+ export declare class ElectronStorageAdapter implements StorageAdapter {
7
+ private readonly filePath;
8
+ private data;
9
+ constructor(options: ElectronStorageAdapterOptions);
10
+ getItem(key: string): Promise<string | null>;
11
+ setItem(key: string, value: string): Promise<void>;
12
+ removeItem(key: string): Promise<void>;
13
+ }
@@ -0,0 +1 @@
1
+ export * from './ElectronStorageAdapter';
@@ -0,0 +1,12 @@
1
+ export * from './adapters/db';
2
+ export * from './adapters/platform';
3
+ export * from './adapters/desktop';
4
+ export * from './adapters/http';
5
+ export * from './adapters/socket';
6
+ export * from './adapters/storage';
7
+ export interface ElectronPlatformModuleStatus {
8
+ platform: 'electron';
9
+ ready: boolean;
10
+ notes: string;
11
+ }
12
+ export declare const OFPLATFORM_ELECTRON_STATUS: ElectronPlatformModuleStatus;
@@ -0,0 +1,9 @@
1
+ import A from"better-sqlite3";var c=class{static escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}static normalizeValue(e){return e==null?null:typeof e=="boolean"?e?1:0:e instanceof Date?e.toISOString():Array.isArray(e)||typeof e=="object"&&e!==null?JSON.stringify(e):e}static buildValueExpr(e,t){if(e.type==="column"){let r=e.tableAlias||t;return{sql:`${this.escapeIdentifier(r)}.${this.escapeIdentifier(e.field)}`,params:[]}}return{sql:"?",params:[this.normalizeValue(e.value)]}}static buildFieldCondition(e,t){if("field"in e){let i=e.alias||t,s=`${this.escapeIdentifier(i)}.${this.escapeIdentifier(e.field)}`,o=e.operator||"=";if(o==="BETWEEN"){if(!Array.isArray(e.value)||e.value.length!==2)throw new Error("BETWEEN requires value tuple [min,max]");return{sql:`${s} BETWEEN ? AND ?`,params:[this.normalizeValue(e.value[0]),this.normalizeValue(e.value[1])]}}if(o==="IN"||o==="NOT IN"){if(!Array.isArray(e.value))throw new Error(`${o} requires array value`);return e.value.length===0?{sql:o==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${s} ${o} (${e.value.map(()=>"?").join(", ")})`,params:e.value.map(a=>this.normalizeValue(a))}}if(o==="IS NULL"||o==="IS NOT NULL")return{sql:`${s} ${o}`,params:[]};if(o==="LIKE"){let l=("likeMode"in e?e.likeMode:"default")==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${s} LIKE${l} ?`,params:[this.normalizeValue(e.value)]}}return{sql:`${s} ${o} ?`,params:[this.normalizeValue(e.value)]}}let r=this.buildValueExpr(e.left,t);if(e.operator==="IS NULL"||e.operator==="IS NOT NULL")return{sql:`${r.sql} ${e.operator}`,params:r.params};let n=this.buildValueExpr(e.right,t);if(e.operator==="IN"||e.operator==="NOT IN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value))throw new Error(`${e.operator} requires literal array on right side`);return e.right.value.length===0?{sql:e.operator==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${r.sql} ${e.operator} (${e.right.value.map(()=>"?").join(", ")})`,params:[...r.params,...e.right.value.map(i=>this.normalizeValue(i))]}}if(e.operator==="BETWEEN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value)||e.right.value.length!==2)throw new Error("BETWEEN requires literal tuple [min,max] on right side");return{sql:`${r.sql} BETWEEN ? AND ?`,params:[...r.params,this.normalizeValue(e.right.value[0]),this.normalizeValue(e.right.value[1])]}}if(e.operator==="LIKE"){let i=e.likeMode==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${r.sql} LIKE${i} ${n.sql}`,params:[...r.params,...n.params]}}return{sql:`${r.sql} ${e.operator} ${n.sql}`,params:[...r.params,...n.params]}}static buildFilter(e,t="t0"){let r=e.and;if(Array.isArray(r)){let i=r.map(s=>this.buildFilter(s,t));return{sql:i.map(s=>`(${s.sql})`).join(" AND "),params:i.flatMap(s=>s.params)}}let n=e.or;if(Array.isArray(n)){let i=n.map(s=>this.buildFilter(s,t));return{sql:i.map(s=>`(${s.sql})`).join(" OR "),params:i.flatMap(s=>s.params)}}if(e!==null&&typeof e=="object"&&!Array.isArray(e)){if("left"in e||"field"in e)return this.buildFieldCondition(e,t);let i=Object.entries(e);if(i.length===0)return{sql:"TRUE",params:[]};let s=i.map(([o,a])=>({sql:`${`${this.escapeIdentifier(t)}.${this.escapeIdentifier(o)}`} = ?`,params:[this.normalizeValue(a)]}));return{sql:s.map(o=>`(${o.sql})`).join(" AND "),params:s.flatMap(o=>o.params)}}throw new Error(`Invalid filter expression: ${JSON.stringify(e)}`)}static buildGroupBy(e,t="t0"){return!e||!e.length?"":` GROUP BY ${e.map(n=>{let i=typeof n.field=="string"?{tableAlias:t,field:n.field}:n.field;return`${this.escapeIdentifier(i.tableAlias)}.${this.escapeIdentifier(i.field)}`}).join(", ")}`}static buildSort(e,t="t0"){return!e||!e.length?"":e.map(r=>{let n=typeof r.field=="string"?{tableAlias:t,field:r.field}:r.field;return`${this.escapeIdentifier(n.tableAlias)}.${this.escapeIdentifier(n.field)} ${r.direction.toUpperCase()}`}).join(", ")}static buildSelect(e,t){let r=(t==null?void 0:t.mainAlias)||"t0",n=(t==null?void 0:t.joins)||[],i=[],s=[];if(t!=null&&t.aggregates&&t.aggregates.length>0)t.groupBy&&t.groupBy.forEach(a=>{let l=typeof a.field=="string"?{tableAlias:r,field:a.field}:a.field;s.push(`${this.escapeIdentifier(l.tableAlias)}.${this.escapeIdentifier(l.field)} AS ${this.escapeIdentifier(`${l.tableAlias}_${l.field}`)}`)}),t.aggregates.forEach(a=>{let l=typeof a.field=="string"?{tableAlias:r,field:a.field}:a.field,u=`${this.escapeIdentifier(l.tableAlias)}.${this.escapeIdentifier(l.field)}`;s.push(`${a.function}(${u}) AS ${this.escapeIdentifier(a.as)}`)});else if(t!=null&&t.fields)for(let[a,l]of Object.entries(t.fields))for(let u of l)s.push(`${this.escapeIdentifier(a)}.${this.escapeIdentifier(u)} AS ${this.escapeIdentifier(`${a}_${u}`)}`);else s.push(`${this.escapeIdentifier(r)}.*`);let o=`SELECT ${s.join(", ")} FROM ${this.escapeIdentifier(e)} AS ${this.escapeIdentifier(r)}`;if(n.forEach(a=>{let l=(a.type||"inner").toUpperCase();if(l==="RIGHT"||l==="FULL")throw new Error(`SQLite adapter does not support ${l} JOIN`);let u=[];a.conditions.forEach(p=>{let h=this.buildValueExpr(p.left,r),g=this.buildValueExpr(p.right,r);u.push(`${h.sql} ${p.operator} ${g.sql}`),i.push(...h.params,...g.params)}),o+=` ${l} JOIN ${this.escapeIdentifier(a.table)} AS ${this.escapeIdentifier(a.alias)} ON ${u.join(" AND ")}`}),t!=null&&t.filters){let a=this.buildFilter(t.filters,r);a.sql&&a.sql!=="TRUE"&&(o+=` WHERE ${a.sql}`,i.push(...a.params))}if(t!=null&&t.aggregates&&t.aggregates.length>0){if(o+=this.buildGroupBy(t.groupBy,r),t.having){let a=this.buildFilter(t.having,r);a.sql&&a.sql!=="TRUE"&&(o+=` HAVING ${a.sql}`,i.push(...a.params))}return{sql:o,params:i}}if(o+=this.buildGroupBy(t==null?void 0:t.groupBy,r),t!=null&&t.having){let a=this.buildFilter(t.having,r);a.sql&&a.sql!=="TRUE"&&(o+=` HAVING ${a.sql}`,i.push(...a.params))}if(t!=null&&t.sort&&t.sort.length){let a=this.buildSort(t.sort,r);a&&(o+=` ORDER BY ${a}`)}return(t==null?void 0:t.limit)!==void 0&&(o+=" LIMIT ?",i.push(t.limit)),(t==null?void 0:t.offset)!==void 0&&(o+=" OFFSET ?",i.push(t.offset)),{sql:o,params:i}}static buildInsert(e,t){let r=Object.keys(t),n=r.map(a=>this.escapeIdentifier(a)).join(", "),i=r.map(()=>"?").join(", "),s=r.map(a=>this.normalizeValue(t[a]));return{sql:`INSERT INTO ${this.escapeIdentifier(e)} (${n}) VALUES (${i})`,params:s}}static buildUpdate(e,t,r){let n=Object.keys(r);if(!n.length)throw new Error("Cannot build UPDATE query with empty updates payload");let i=n.map(a=>`${this.escapeIdentifier(a)} = ?`).join(", "),s=n.map(a=>this.normalizeValue(r[a]));return{sql:`UPDATE ${this.escapeIdentifier(e)} SET ${i} WHERE id = ?`,params:[...s,t]}}static buildDelete(e,t){return{sql:`DELETE FROM ${this.escapeIdentifier(e)} WHERE id = ?`,params:[t]}}};var d=class{constructor(e){this.db=new A(e)}initMetaTable(){this.db.exec(`
2
+ CREATE TABLE IF NOT EXISTS _meta (
3
+ key TEXT PRIMARY KEY,
4
+ value TEXT
5
+ );
6
+ CREATE TABLE IF NOT EXISTS schema_version (
7
+ version INTEGER NOT NULL
8
+ );
9
+ `)}run(e,t=[]){return this.db.prepare(e).run(...t)}get(e,t=[]){return this.db.prepare(e).get(...t)}all(e,t=[]){return this.db.prepare(e).all(...t)}transaction(e){return this.db.transaction(e)()}escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}mapType(e){switch(e){case"string":return"TEXT";case"string[]":return"TEXT";case"number":return"REAL";case"boolean":return"INTEGER";case"json":return"TEXT";case"date":return"TEXT";default:return"TEXT"}}parseRow(e,t){let r={};for(let[n,i]of Object.entries(e!=null?e:{})){let s=t[n],o=i;if(i!=null&&s){if(s.type==="boolean")o=!!i;else if(s.type==="date"&&typeof i=="string"){let a=new Date(i);Number.isNaN(a.getTime())||(o=a)}else if((s.type==="json"||s.type==="string[]")&&typeof i=="string")try{o=JSON.parse(i)}catch(a){}}r[n]=o}return r}denormalizeRow(e,t="t0"){let r={},n={};for(let[i,s]of Object.entries(e!=null?e:{})){if(!i.includes("_")){r[i]=s;continue}let[o,a]=i.split("_",2);if(!o||!a){r[i]=s;continue}o===t?r[a]=s:(n[o]||(n[o]={}),n[o][a]=s)}return{...r,...n}}buildFilter(e,t="t0"){return c.buildFilter(e,t)}buildGroupBy(e,t="t0"){return c.buildGroupBy(e,t)}buildSort(e,t="t0"){return c.buildSort(e,t)}buildSelect(e,t){return c.buildSelect(e,t)}buildInsert(e,t){return c.buildInsert(e,t)}buildUpdate(e,t,r){return c.buildUpdate(e,t,r)}buildDelete(e,t){return c.buildDelete(e,t)}};var E=class{constructor(e){this.savepointDepth=0;this.schemaMap=new Map;this.executor=new d(e.dbPath),this.executor.initMetaTable()}async getSchemaVersion(){let e=this.executor.get("SELECT version FROM schema_version LIMIT 1");return e?e.version:(this.executor.run("INSERT INTO schema_version (version) VALUES (?)",[0]),0)}async setSchemaVersion(e){if(!this.executor.get("SELECT 1 FROM schema_version LIMIT 1")){this.executor.run("INSERT INTO schema_version (version) VALUES (?)",[e]);return}this.executor.run("UPDATE schema_version SET version = ?",[e])}async addTable(e){let t=e.columns.map(r=>{let n=`${this.executor.escapeIdentifier(r.name)} ${this.executor.mapType(r.type)}`;return r.isOptional||(n+=" NOT NULL"),r.name==="id"&&(n+=" PRIMARY KEY"),n}).join(", ");this.executor.run(`CREATE TABLE IF NOT EXISTS ${this.executor.escapeIdentifier(e.name)} (${t})`);for(let r of e.columns){if(!r.isIndexed)continue;let n=`idx_${e.name}_${r.name}`;this.executor.run(`CREATE INDEX IF NOT EXISTS ${this.executor.escapeIdentifier(n)} ON ${this.executor.escapeIdentifier(e.name)}(${this.executor.escapeIdentifier(r.name)})`)}this.schemaMap.set(e.name,e)}async addColumn(e,t){if(this.executor.all(`PRAGMA table_info(${this.executor.escapeIdentifier(e)})`).some(s=>s.name===t.name))return;let n=`ALTER TABLE ${this.executor.escapeIdentifier(e)} ADD COLUMN ${this.executor.escapeIdentifier(t.name)} ${this.executor.mapType(t.type)}`;if(!t.isOptional){let s=t.type==="boolean"?"0":t.type==="number"?"0.0":"''";n+=` DEFAULT ${s} NOT NULL`}if(this.executor.run(n),t.isIndexed){let s=`idx_${e}_${t.name}`;this.executor.run(`CREATE INDEX IF NOT EXISTS ${this.executor.escapeIdentifier(s)} ON ${this.executor.escapeIdentifier(e)}(${this.executor.escapeIdentifier(t.name)})`)}let i=this.schemaMap.get(e);i&&!i.columns.find(s=>s.name===t.name)&&(i.columns.push(t),this.schemaMap.set(e,i))}async get(e,t){var s;let r,n;return typeof t=="string"?n={id:t}:(r=t,n=r.filters),(s=(await this.query(e,{...r,filters:n,limit:1}))[0])!=null?s:null}async query(e,t){var a;let{sql:r,params:n}=this.executor.buildSelect(e,t),i=this.executor.all(r,n);if((a=t==null?void 0:t.aggregates)!=null&&a.length||t!=null&&t.fields)return i;let s=this.schemaMap.get(e);if(!s)return i;let o=Object.fromEntries(s.columns.map(l=>[l.name,l]));return i.map(l=>{let u=this.executor.denormalizeRow(l);return this.executor.parseRow(u,o)})}async create(e,t){var u,p;let{sql:r,params:n}=this.executor.buildInsert(e,t),i=this.executor.run(r,n),s=(p=t==null?void 0:t.id)!=null?p:(u=i.lastInsertRowid)==null?void 0:u.toString(),o=s?{...t,id:s}:{...t},a=this.schemaMap.get(e);if(!a)return o;let l=Object.fromEntries(a.columns.map(h=>[h.name,h]));return this.executor.parseRow(o,l)}async update(e,t,r){if(!Object.keys(r).length){let o=await this.get(e,t);if(!o)throw new Error(`Record not found: ${e}#${t}`);return o}let{sql:n,params:i}=this.executor.buildUpdate(e,t,r);this.executor.run(n,i);let s=await this.get(e,t);if(!s)throw new Error(`Record not found after update: ${e}#${t}`);return s}async delete(e,t){let{sql:r,params:n}=this.executor.buildDelete(e,t);this.executor.run(r,n)}async bulkCreate(e,t){return t.length?this.transaction(async r=>{let n=[];for(let i of t)n.push(await r.create(e,i));return n}):[]}async bulkUpdate(e,t){return t.length?this.transaction(async r=>{let n=[];for(let i of t)Object.keys(i.updates).length&&n.push(await r.update(e,i.id,i.updates));return n}):[]}async transaction(e){let t=this.savepointDepth===0,r=t?"BEGIN IMMEDIATE":`SAVEPOINT sp${this.savepointDepth}`;this.executor.run(r),this.savepointDepth+=1;try{let n=await e(this),i=t?"COMMIT":`RELEASE SAVEPOINT sp${this.savepointDepth-1}`;return this.executor.run(i),n}catch(n){let i=t?"ROLLBACK":`ROLLBACK TO SAVEPOINT sp${this.savepointDepth-1}`;try{this.executor.run(i)}catch(s){}throw n}finally{this.savepointDepth-=1}}};import{createHash as S}from"node:crypto";var y=class{getEnvVar(e){return process.env[e]}isOnline(){return typeof navigator!="undefined"&&typeof navigator.onLine!="undefined"?navigator.onLine:!0}async hashPin(e){return S("sha256").update(e).digest("hex")}async verifyPin(e,t){return await this.hashPin(e)===t}};var T=class{constructor(e){this.options=e}configure(e){}createChannel(e){}localNotification(e){this.options.notify(e)}};import b from"node:fs/promises";import x from"node:path";var I=class{constructor(e){this.options=e}getDocumentPath(e){return x.join(this.options.documentsPath,e)}async writeFile(e,t){await b.writeFile(e,t,"utf8")}async share(e){var t,r;await((r=(t=this.options).revealFile)==null?void 0:r.call(t,e))}};var J=!1;var K=!1;import m from"node:fs";import N from"node:path";var $=class{constructor(e){var r;let t=(r=e.fileName)!=null?r:"storage.json";m.mkdirSync(e.storageDir,{recursive:!0}),this.filePath=N.join(e.storageDir,t);try{this.data=JSON.parse(m.readFileSync(this.filePath,"utf-8"))}catch{this.data={}}}async getItem(e){let t=this.data[e];return t!==void 0?t:null}async setItem(e,t){this.data[e]=t,await m.promises.writeFile(this.filePath,JSON.stringify(this.data,null,2),"utf-8")}async removeItem(e){delete this.data[e],await m.promises.writeFile(this.filePath,JSON.stringify(this.data,null,2),"utf-8")}};var ie={platform:"electron",ready:!1,notes:"Scaffold only. Add concrete adapters (db/http/socket/storage) in this package."};export{I as ElectronExportAdapter,T as ElectronNotificationAdapter,y as ElectronPlatformAdapter,$ as ElectronStorageAdapter,J as OFPLATFORM_ELECTRON_HTTP_ADAPTERS_READY,K as OFPLATFORM_ELECTRON_SOCKET_ADAPTERS_READY,ie as OFPLATFORM_ELECTRON_STATUS,E as SQLiteAdapter,d as SQLiteExecutor,c as SQLiteQueryBuilder};
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";var L=Object.create;var E=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,D=Object.prototype.hasOwnProperty;var F=(u,e)=>{for(var t in e)E(u,t,{get:e[t],enumerable:!0})},b=(u,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of w(e))!D.call(u,i)&&i!==t&&E(u,i,{get:()=>e[i],enumerable:!(r=q(e,i))||r.enumerable});return u};var d=(u,e,t)=>(t=u!=null?L(P(u)):{},b(e||!u||!u.__esModule?E(t,"default",{value:u,enumerable:!0}):t,u)),C=u=>b(E({},"__esModule",{value:!0}),u);var M={};F(M,{ElectronExportAdapter:()=>$,ElectronNotificationAdapter:()=>I,ElectronPlatformAdapter:()=>T,ElectronStorageAdapter:()=>A,OFPLATFORM_ELECTRON_HTTP_ADAPTERS_READY:()=>ie,OFPLATFORM_ELECTRON_SOCKET_ADAPTERS_READY:()=>ae,OFPLATFORM_ELECTRON_STATUS:()=>V,SQLiteAdapter:()=>y,SQLiteExecutor:()=>m,SQLiteQueryBuilder:()=>f});module.exports=C(M);var x=d(require("better-sqlite3"));var f=class{static escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}static normalizeValue(e){return e==null?null:typeof e=="boolean"?e?1:0:e instanceof Date?e.toISOString():Array.isArray(e)||typeof e=="object"&&e!==null?JSON.stringify(e):e}static buildValueExpr(e,t){if(e.type==="column"){let r=e.tableAlias||t;return{sql:`${this.escapeIdentifier(r)}.${this.escapeIdentifier(e.field)}`,params:[]}}return{sql:"?",params:[this.normalizeValue(e.value)]}}static buildFieldCondition(e,t){if("field"in e){let s=e.alias||t,a=`${this.escapeIdentifier(s)}.${this.escapeIdentifier(e.field)}`,o=e.operator||"=";if(o==="BETWEEN"){if(!Array.isArray(e.value)||e.value.length!==2)throw new Error("BETWEEN requires value tuple [min,max]");return{sql:`${a} BETWEEN ? AND ?`,params:[this.normalizeValue(e.value[0]),this.normalizeValue(e.value[1])]}}if(o==="IN"||o==="NOT IN"){if(!Array.isArray(e.value))throw new Error(`${o} requires array value`);return e.value.length===0?{sql:o==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${a} ${o} (${e.value.map(()=>"?").join(", ")})`,params:e.value.map(n=>this.normalizeValue(n))}}if(o==="IS NULL"||o==="IS NOT NULL")return{sql:`${a} ${o}`,params:[]};if(o==="LIKE"){let l=("likeMode"in e?e.likeMode:"default")==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${a} LIKE${l} ?`,params:[this.normalizeValue(e.value)]}}return{sql:`${a} ${o} ?`,params:[this.normalizeValue(e.value)]}}let r=this.buildValueExpr(e.left,t);if(e.operator==="IS NULL"||e.operator==="IS NOT NULL")return{sql:`${r.sql} ${e.operator}`,params:r.params};let i=this.buildValueExpr(e.right,t);if(e.operator==="IN"||e.operator==="NOT IN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value))throw new Error(`${e.operator} requires literal array on right side`);return e.right.value.length===0?{sql:e.operator==="IN"?"FALSE":"TRUE",params:[]}:{sql:`${r.sql} ${e.operator} (${e.right.value.map(()=>"?").join(", ")})`,params:[...r.params,...e.right.value.map(s=>this.normalizeValue(s))]}}if(e.operator==="BETWEEN"){if(e.right.type!=="literal"||!Array.isArray(e.right.value)||e.right.value.length!==2)throw new Error("BETWEEN requires literal tuple [min,max] on right side");return{sql:`${r.sql} BETWEEN ? AND ?`,params:[...r.params,this.normalizeValue(e.right.value[0]),this.normalizeValue(e.right.value[1])]}}if(e.operator==="LIKE"){let s=e.likeMode==="case-sensitive"?" COLLATE BINARY":"";return{sql:`${r.sql} LIKE${s} ${i.sql}`,params:[...r.params,...i.params]}}return{sql:`${r.sql} ${e.operator} ${i.sql}`,params:[...r.params,...i.params]}}static buildFilter(e,t="t0"){let r=e.and;if(Array.isArray(r)){let s=r.map(a=>this.buildFilter(a,t));return{sql:s.map(a=>`(${a.sql})`).join(" AND "),params:s.flatMap(a=>a.params)}}let i=e.or;if(Array.isArray(i)){let s=i.map(a=>this.buildFilter(a,t));return{sql:s.map(a=>`(${a.sql})`).join(" OR "),params:s.flatMap(a=>a.params)}}if(e!==null&&typeof e=="object"&&!Array.isArray(e)){if("left"in e||"field"in e)return this.buildFieldCondition(e,t);let s=Object.entries(e);if(s.length===0)return{sql:"TRUE",params:[]};let a=s.map(([o,n])=>({sql:`${`${this.escapeIdentifier(t)}.${this.escapeIdentifier(o)}`} = ?`,params:[this.normalizeValue(n)]}));return{sql:a.map(o=>`(${o.sql})`).join(" AND "),params:a.flatMap(o=>o.params)}}throw new Error(`Invalid filter expression: ${JSON.stringify(e)}`)}static buildGroupBy(e,t="t0"){return!e||!e.length?"":` GROUP BY ${e.map(i=>{let s=typeof i.field=="string"?{tableAlias:t,field:i.field}:i.field;return`${this.escapeIdentifier(s.tableAlias)}.${this.escapeIdentifier(s.field)}`}).join(", ")}`}static buildSort(e,t="t0"){return!e||!e.length?"":e.map(r=>{let i=typeof r.field=="string"?{tableAlias:t,field:r.field}:r.field;return`${this.escapeIdentifier(i.tableAlias)}.${this.escapeIdentifier(i.field)} ${r.direction.toUpperCase()}`}).join(", ")}static buildSelect(e,t){let r=(t==null?void 0:t.mainAlias)||"t0",i=(t==null?void 0:t.joins)||[],s=[],a=[];if(t!=null&&t.aggregates&&t.aggregates.length>0)t.groupBy&&t.groupBy.forEach(n=>{let l=typeof n.field=="string"?{tableAlias:r,field:n.field}:n.field;a.push(`${this.escapeIdentifier(l.tableAlias)}.${this.escapeIdentifier(l.field)} AS ${this.escapeIdentifier(`${l.tableAlias}_${l.field}`)}`)}),t.aggregates.forEach(n=>{let l=typeof n.field=="string"?{tableAlias:r,field:n.field}:n.field,c=`${this.escapeIdentifier(l.tableAlias)}.${this.escapeIdentifier(l.field)}`;a.push(`${n.function}(${c}) AS ${this.escapeIdentifier(n.as)}`)});else if(t!=null&&t.fields)for(let[n,l]of Object.entries(t.fields))for(let c of l)a.push(`${this.escapeIdentifier(n)}.${this.escapeIdentifier(c)} AS ${this.escapeIdentifier(`${n}_${c}`)}`);else a.push(`${this.escapeIdentifier(r)}.*`);let o=`SELECT ${a.join(", ")} FROM ${this.escapeIdentifier(e)} AS ${this.escapeIdentifier(r)}`;if(i.forEach(n=>{let l=(n.type||"inner").toUpperCase();if(l==="RIGHT"||l==="FULL")throw new Error(`SQLite adapter does not support ${l} JOIN`);let c=[];n.conditions.forEach(p=>{let h=this.buildValueExpr(p.left,r),S=this.buildValueExpr(p.right,r);c.push(`${h.sql} ${p.operator} ${S.sql}`),s.push(...h.params,...S.params)}),o+=` ${l} JOIN ${this.escapeIdentifier(n.table)} AS ${this.escapeIdentifier(n.alias)} ON ${c.join(" AND ")}`}),t!=null&&t.filters){let n=this.buildFilter(t.filters,r);n.sql&&n.sql!=="TRUE"&&(o+=` WHERE ${n.sql}`,s.push(...n.params))}if(t!=null&&t.aggregates&&t.aggregates.length>0){if(o+=this.buildGroupBy(t.groupBy,r),t.having){let n=this.buildFilter(t.having,r);n.sql&&n.sql!=="TRUE"&&(o+=` HAVING ${n.sql}`,s.push(...n.params))}return{sql:o,params:s}}if(o+=this.buildGroupBy(t==null?void 0:t.groupBy,r),t!=null&&t.having){let n=this.buildFilter(t.having,r);n.sql&&n.sql!=="TRUE"&&(o+=` HAVING ${n.sql}`,s.push(...n.params))}if(t!=null&&t.sort&&t.sort.length){let n=this.buildSort(t.sort,r);n&&(o+=` ORDER BY ${n}`)}return(t==null?void 0:t.limit)!==void 0&&(o+=" LIMIT ?",s.push(t.limit)),(t==null?void 0:t.offset)!==void 0&&(o+=" OFFSET ?",s.push(t.offset)),{sql:o,params:s}}static buildInsert(e,t){let r=Object.keys(t),i=r.map(n=>this.escapeIdentifier(n)).join(", "),s=r.map(()=>"?").join(", "),a=r.map(n=>this.normalizeValue(t[n]));return{sql:`INSERT INTO ${this.escapeIdentifier(e)} (${i}) VALUES (${s})`,params:a}}static buildUpdate(e,t,r){let i=Object.keys(r);if(!i.length)throw new Error("Cannot build UPDATE query with empty updates payload");let s=i.map(n=>`${this.escapeIdentifier(n)} = ?`).join(", "),a=i.map(n=>this.normalizeValue(r[n]));return{sql:`UPDATE ${this.escapeIdentifier(e)} SET ${s} WHERE id = ?`,params:[...a,t]}}static buildDelete(e,t){return{sql:`DELETE FROM ${this.escapeIdentifier(e)} WHERE id = ?`,params:[t]}}};var m=class{constructor(e){this.db=new x.default(e)}initMetaTable(){this.db.exec(`
2
+ CREATE TABLE IF NOT EXISTS _meta (
3
+ key TEXT PRIMARY KEY,
4
+ value TEXT
5
+ );
6
+ CREATE TABLE IF NOT EXISTS schema_version (
7
+ version INTEGER NOT NULL
8
+ );
9
+ `)}run(e,t=[]){return this.db.prepare(e).run(...t)}get(e,t=[]){return this.db.prepare(e).get(...t)}all(e,t=[]){return this.db.prepare(e).all(...t)}transaction(e){return this.db.transaction(e)()}escapeIdentifier(e){if(!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(e))throw new Error(`Invalid identifier: "${e}"`);return`"${e}"`}mapType(e){switch(e){case"string":return"TEXT";case"string[]":return"TEXT";case"number":return"REAL";case"boolean":return"INTEGER";case"json":return"TEXT";case"date":return"TEXT";default:return"TEXT"}}parseRow(e,t){let r={};for(let[i,s]of Object.entries(e!=null?e:{})){let a=t[i],o=s;if(s!=null&&a){if(a.type==="boolean")o=!!s;else if(a.type==="date"&&typeof s=="string"){let n=new Date(s);Number.isNaN(n.getTime())||(o=n)}else if((a.type==="json"||a.type==="string[]")&&typeof s=="string")try{o=JSON.parse(s)}catch(n){}}r[i]=o}return r}denormalizeRow(e,t="t0"){let r={},i={};for(let[s,a]of Object.entries(e!=null?e:{})){if(!s.includes("_")){r[s]=a;continue}let[o,n]=s.split("_",2);if(!o||!n){r[s]=a;continue}o===t?r[n]=a:(i[o]||(i[o]={}),i[o][n]=a)}return{...r,...i}}buildFilter(e,t="t0"){return f.buildFilter(e,t)}buildGroupBy(e,t="t0"){return f.buildGroupBy(e,t)}buildSort(e,t="t0"){return f.buildSort(e,t)}buildSelect(e,t){return f.buildSelect(e,t)}buildInsert(e,t){return f.buildInsert(e,t)}buildUpdate(e,t,r){return f.buildUpdate(e,t,r)}buildDelete(e,t){return f.buildDelete(e,t)}};var y=class{constructor(e){this.savepointDepth=0;this.schemaMap=new Map;this.executor=new m(e.dbPath),this.executor.initMetaTable()}async getSchemaVersion(){let e=this.executor.get("SELECT version FROM schema_version LIMIT 1");return e?e.version:(this.executor.run("INSERT INTO schema_version (version) VALUES (?)",[0]),0)}async setSchemaVersion(e){if(!this.executor.get("SELECT 1 FROM schema_version LIMIT 1")){this.executor.run("INSERT INTO schema_version (version) VALUES (?)",[e]);return}this.executor.run("UPDATE schema_version SET version = ?",[e])}async addTable(e){let t=e.columns.map(r=>{let i=`${this.executor.escapeIdentifier(r.name)} ${this.executor.mapType(r.type)}`;return r.isOptional||(i+=" NOT NULL"),r.name==="id"&&(i+=" PRIMARY KEY"),i}).join(", ");this.executor.run(`CREATE TABLE IF NOT EXISTS ${this.executor.escapeIdentifier(e.name)} (${t})`);for(let r of e.columns){if(!r.isIndexed)continue;let i=`idx_${e.name}_${r.name}`;this.executor.run(`CREATE INDEX IF NOT EXISTS ${this.executor.escapeIdentifier(i)} ON ${this.executor.escapeIdentifier(e.name)}(${this.executor.escapeIdentifier(r.name)})`)}this.schemaMap.set(e.name,e)}async addColumn(e,t){if(this.executor.all(`PRAGMA table_info(${this.executor.escapeIdentifier(e)})`).some(a=>a.name===t.name))return;let i=`ALTER TABLE ${this.executor.escapeIdentifier(e)} ADD COLUMN ${this.executor.escapeIdentifier(t.name)} ${this.executor.mapType(t.type)}`;if(!t.isOptional){let a=t.type==="boolean"?"0":t.type==="number"?"0.0":"''";i+=` DEFAULT ${a} NOT NULL`}if(this.executor.run(i),t.isIndexed){let a=`idx_${e}_${t.name}`;this.executor.run(`CREATE INDEX IF NOT EXISTS ${this.executor.escapeIdentifier(a)} ON ${this.executor.escapeIdentifier(e)}(${this.executor.escapeIdentifier(t.name)})`)}let s=this.schemaMap.get(e);s&&!s.columns.find(a=>a.name===t.name)&&(s.columns.push(t),this.schemaMap.set(e,s))}async get(e,t){var a;let r,i;return typeof t=="string"?i={id:t}:(r=t,i=r.filters),(a=(await this.query(e,{...r,filters:i,limit:1}))[0])!=null?a:null}async query(e,t){var n;let{sql:r,params:i}=this.executor.buildSelect(e,t),s=this.executor.all(r,i);if((n=t==null?void 0:t.aggregates)!=null&&n.length||t!=null&&t.fields)return s;let a=this.schemaMap.get(e);if(!a)return s;let o=Object.fromEntries(a.columns.map(l=>[l.name,l]));return s.map(l=>{let c=this.executor.denormalizeRow(l);return this.executor.parseRow(c,o)})}async create(e,t){var c,p;let{sql:r,params:i}=this.executor.buildInsert(e,t),s=this.executor.run(r,i),a=(p=t==null?void 0:t.id)!=null?p:(c=s.lastInsertRowid)==null?void 0:c.toString(),o=a?{...t,id:a}:{...t},n=this.schemaMap.get(e);if(!n)return o;let l=Object.fromEntries(n.columns.map(h=>[h.name,h]));return this.executor.parseRow(o,l)}async update(e,t,r){if(!Object.keys(r).length){let o=await this.get(e,t);if(!o)throw new Error(`Record not found: ${e}#${t}`);return o}let{sql:i,params:s}=this.executor.buildUpdate(e,t,r);this.executor.run(i,s);let a=await this.get(e,t);if(!a)throw new Error(`Record not found after update: ${e}#${t}`);return a}async delete(e,t){let{sql:r,params:i}=this.executor.buildDelete(e,t);this.executor.run(r,i)}async bulkCreate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let s of t)i.push(await r.create(e,s));return i}):[]}async bulkUpdate(e,t){return t.length?this.transaction(async r=>{let i=[];for(let s of t)Object.keys(s.updates).length&&i.push(await r.update(e,s.id,s.updates));return i}):[]}async transaction(e){let t=this.savepointDepth===0,r=t?"BEGIN IMMEDIATE":`SAVEPOINT sp${this.savepointDepth}`;this.executor.run(r),this.savepointDepth+=1;try{let i=await e(this),s=t?"COMMIT":`RELEASE SAVEPOINT sp${this.savepointDepth-1}`;return this.executor.run(s),i}catch(i){let s=t?"ROLLBACK":`ROLLBACK TO SAVEPOINT sp${this.savepointDepth-1}`;try{this.executor.run(s)}catch(a){}throw i}finally{this.savepointDepth-=1}}};var N=require("node:crypto"),T=class{getEnvVar(e){return process.env[e]}isOnline(){return typeof navigator!="undefined"&&typeof navigator.onLine!="undefined"?navigator.onLine:!0}async hashPin(e){return(0,N.createHash)("sha256").update(e).digest("hex")}async verifyPin(e,t){return await this.hashPin(e)===t}};var I=class{constructor(e){this.options=e}configure(e){}createChannel(e){}localNotification(e){this.options.notify(e)}};var O=d(require("node:fs/promises")),v=d(require("node:path")),$=class{constructor(e){this.options=e}getDocumentPath(e){return v.default.join(this.options.documentsPath,e)}async writeFile(e,t){await O.default.writeFile(e,t,"utf8")}async share(e){var t,r;await((r=(t=this.options).revealFile)==null?void 0:r.call(t,e))}};var ie=!1;var ae=!1;var g=d(require("node:fs")),R=d(require("node:path")),A=class{constructor(e){var r;let t=(r=e.fileName)!=null?r:"storage.json";g.default.mkdirSync(e.storageDir,{recursive:!0}),this.filePath=R.default.join(e.storageDir,t);try{this.data=JSON.parse(g.default.readFileSync(this.filePath,"utf-8"))}catch{this.data={}}}async getItem(e){let t=this.data[e];return t!==void 0?t:null}async setItem(e,t){this.data[e]=t,await g.default.promises.writeFile(this.filePath,JSON.stringify(this.data,null,2),"utf-8")}async removeItem(e){delete this.data[e],await g.default.promises.writeFile(this.filePath,JSON.stringify(this.data,null,2),"utf-8")}};var V={platform:"electron",ready:!1,notes:"Scaffold only. Add concrete adapters (db/http/socket/storage) in this package."};0&&(module.exports={ElectronExportAdapter,ElectronNotificationAdapter,ElectronPlatformAdapter,ElectronStorageAdapter,OFPLATFORM_ELECTRON_HTTP_ADAPTERS_READY,OFPLATFORM_ELECTRON_SOCKET_ADAPTERS_READY,OFPLATFORM_ELECTRON_STATUS,SQLiteAdapter,SQLiteExecutor,SQLiteQueryBuilder});
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "ofplatform-electron",
3
+ "version": "0.1.0-alpha.0",
4
+ "private": false,
5
+ "description": "Electron platform adapters for of* host apps.",
6
+ "author": {
7
+ "name": "Agus Made",
8
+ "email": "krisnaparta@gmail.com"
9
+ },
10
+ "main": "dist/index.js",
11
+ "module": "dist/index.esm.js",
12
+ "types": "dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.esm.js",
17
+ "require": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
25
+ "build": "npm run clean && tsc -p tsconfig.build.json && node esbuild.config.js",
26
+ "typecheck": "tsc -p tsconfig.json --noEmit",
27
+ "ci:check": "npm run typecheck && npm run build",
28
+ "prepublishOnly": "npm run ci:check",
29
+ "prepack": "npm run build",
30
+ "bundle": "node esbuild.config.js"
31
+ },
32
+ "dependencies": {
33
+ "better-sqlite3": "^11.8.1",
34
+ "ofcore": "0.1.0-alpha.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/better-sqlite3": "^7.6.12",
38
+ "typescript": "^5.9.3",
39
+ "esbuild": "^0.25.11"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ }
44
+ }