@fenaura/sdk 0.2.2 → 0.2.3

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/bin/init.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // npx @fenaura/auth-init run from your frontend folder.
2
+ // npx @fenaura/auth-init - run from your frontend folder.
3
3
  // Writes the /fenaura/* proxy files your host needs. Idempotent.
4
4
  const fs = require('fs');
5
5
  const path = require('path');
@@ -17,7 +17,7 @@ async function confirmRoot() {
17
17
  const answer = await new Promise((resolve) => rl.question(`No package.json here. Is this your frontend root? (${cwd}) [Y/n] `, resolve));
18
18
  rl.close();
19
19
  if (!/^(y|yes)?\s*$/i.test(answer || '')) {
20
- console.error('Aborted cd into your frontend folder first, then re-run.');
20
+ console.error('Aborted - cd into your frontend folder first, then re-run.');
21
21
  process.exit(1);
22
22
  }
23
23
  }
@@ -87,7 +87,7 @@ async function main() {
87
87
  const vp = path.join(cwd, viteCfg);
88
88
  let src = fs.readFileSync(vp, 'utf8');
89
89
  if (src.includes('/fenaura')) {
90
- // already has proxy correct it if API mismatched (tampered)
90
+ // already has proxy - correct it if API mismatched (tampered)
91
91
  if (src.includes(API)) {
92
92
  report.push(`kept ${viteCfg} (proxy already present)`);
93
93
  } else {
@@ -98,26 +98,26 @@ async function main() {
98
98
  report.push(`fixed ${viteCfg} (corrected tampered API target → ${API})`);
99
99
  }
100
100
  } else if (/server\s*:\s*\{[^}]*proxy\s*:/.test(src)) {
101
- // server.proxy exists append our entry inside it
101
+ // server.proxy exists - append our entry inside it
102
102
  src = src.replace(/(proxy\s*:\s*\{)/, `$1\n ${PROXY_BLOCK},`);
103
103
  fs.writeFileSync(vp, src);
104
104
  report.push(`wrote ${viteCfg} (appended to server.proxy)`);
105
105
  } else if (/defineConfig\(\s*\{/.test(src)) {
106
- // no server key add one
106
+ // no server key - add one
107
107
  src = src.replace(/(defineConfig\(\s*\{)/, `$1\n server: {\n proxy: {\n ${PROXY_BLOCK},\n },\n },`);
108
108
  fs.writeFileSync(vp, src);
109
109
  report.push(`wrote ${viteCfg} (added server.proxy)`);
110
110
  } else {
111
- report.push(`${viteCfg} has no defineConfig({...) add manually:`);
111
+ report.push(`${viteCfg} has no defineConfig({...) - add manually:`);
112
112
  report.push(` ${PROXY_BLOCK}`);
113
113
  }
114
114
  } else if (nextCfg) {
115
- report.push('next detected rewrites() needs an async function; add manually:');
115
+ report.push('next detected - rewrites() needs an async function; add manually:');
116
116
  report.push(` { source: '/fenaura/:path*', destination: '${API}/:path*' }`);
117
117
  } else if (fallbackVite) {
118
118
  // SvelteKit / Astro / Nuxt: they all proxy via vite underneath, but config shape varies
119
119
  // For these, ensure public/_redirects covers prod and print the vite snippet for dev
120
- report.push(`${fallbackVite} detected (vite-based) add to vite server.proxy for dev:`);
120
+ report.push(`${fallbackVite} detected (vite-based) - add to vite server.proxy for dev:`);
121
121
  report.push(` ${PROXY_BLOCK}`);
122
122
  report.push(' (public/_redirects already covers prod)');
123
123
  } else {
@@ -142,7 +142,7 @@ async function main() {
142
142
  } else {
143
143
  proxy.enable = true;
144
144
  proxy.baseUri = '/fenaura';
145
- // Via local nginx (:3000), which strips /fenaura Live Server can't rewrite paths itself.
145
+ // Via local nginx (:3000), which strips /fenaura - Live Server can't rewrite paths itself.
146
146
  proxy.proxyUri = 'http://localhost:3000/fenaura';
147
147
  fs.mkdirSync(vsDir, { recursive: true });
148
148
  fs.writeFileSync(vsPath, JSON.stringify(settings, null, 2) + '\n');
@@ -151,9 +151,9 @@ async function main() {
151
151
  }
152
152
 
153
153
  const wroteAny = report.some(r => r.startsWith('wrote') || r.startsWith('fixed'));
154
- console.log('\n' + (wroteAny ? '✓ Fenaura auth proxy setup done' : '✓ Fenaura auth proxy setup already configured') + '\n' + report.map((r) => ' ' + r).join('\n'));
155
- if (wroteAny) console.log('\n → You should see "wrote public/_redirects" and "wrote vercel.json" above (or "fixed …" if a tampered file was corrected). If you see "kept … (already present)" it was already configured you are good to go.');
156
- else console.log('\n → Nothing to write proxy files were already present. You are good to go.');
154
+ console.log('\n' + (wroteAny ? '✓ Fenaura auth proxy setup - done' : '✓ Fenaura auth proxy setup - already configured') + '\n' + report.map((r) => ' ' + r).join('\n'));
155
+ if (wroteAny) console.log('\n → You should see "wrote public/_redirects" and "wrote vercel.json" above (or "fixed …" if a tampered file was corrected). If you see "kept … (already present)" it was already configured - you are good to go.');
156
+ else console.log('\n → Nothing to write - proxy files were already present. You are good to go.');
157
157
  console.log('\nFrontend calls (copy-paste, project ID from dashboard):');
158
158
  console.log(" fetch('/fenaura/auth/dp/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ email, password, project_id: '<PROJECT_ID>' }) })");
159
159
 
@@ -185,13 +185,13 @@ async function main() {
185
185
  const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
186
186
  const hasSdk = deps['@fenaura/client'] || deps['@fenaura/sdk'] || deps['fenaura'];
187
187
  if (!hasSdk) {
188
- console.log('\n → SDK not in package.json installing @fenaura/sdk ...');
188
+ console.log('\n → SDK not in package.json - installing @fenaura/sdk ...');
189
189
  const { spawnSync } = require('child_process');
190
190
  const res = spawnSync('npm', ['install', '@fenaura/sdk', '--save'], { stdio: 'inherit', cwd });
191
- if (res.status !== 0) console.log(' (auto-install failed run: npm install @fenaura/sdk)');
191
+ if (res.status !== 0) console.log(' (auto-install failed - run: npm install @fenaura/sdk)');
192
192
  else console.log(' ✓ @fenaura/client installed');
193
193
  }
194
- } catch { /* no package.json or parse fail skip auto-install */ }
194
+ } catch { /* no package.json or parse fail - skip auto-install */ }
195
195
 
196
196
  // 6. Detect how the dev server is running + print next steps.
197
197
  const net = require('net');
@@ -213,10 +213,10 @@ async function main() {
213
213
  }
214
214
  console.log('\nNext steps:');
215
215
  console.log(` 1. Start dev: ${startCmd} (detected stack: ${stack})`);
216
- console.log(` 2. Open the page (local ports answering now: ${open.length ? open.join(', ') : 'none yet start the server first'})`);
217
- console.log(' 3. Click login expect session_minted + a fenaura_eusid_* cookie on YOUR domain (devtools → Application → Cookies).');
216
+ console.log(` 2. Open the page (local ports answering now: ${open.length ? open.join(', ') : 'none yet - start the server first'})`);
217
+ console.log(' 3. Click login - expect session_minted + a fenaura_eusid_* cookie on YOUR domain (devtools → Application → Cookies).');
218
218
  console.log(' 4. Public URL test: forward the dev port in VS Code (Ports → Public, protocol http) and open the tunnel link.');
219
- console.log(' 5. Deploy: the _redirects/vercel.json files proxy /fenaura/* in prod no code changes.\n');
219
+ console.log(' 5. Deploy: the _redirects/vercel.json files proxy /fenaura/* in prod - no code changes.\n');
220
220
  })();
221
221
 
222
222
  }
@@ -157,7 +157,7 @@ interface CompressResult {
157
157
  mode: string;
158
158
  originalSize: number;
159
159
  compressedSize: number;
160
- /** Real MIME of `file` never assumed, always the produced bytes' type */
160
+ /** Real MIME of `file` - never assumed, always the produced bytes' type */
161
161
  contentType: string;
162
162
  }
163
163
 
@@ -223,7 +223,7 @@ interface QueryBuilder<T = Record<string, any>> {
223
223
  select(columns?: string): QueryBuilder<T>;
224
224
  insert(data: T | T[]): Promise<QueryResult & { data: T[] }>;
225
225
  update(data: Partial<T>): QueryBuilder<T>;
226
- /** Single object only arrays are rejected 400 server-side. */
226
+ /** Single object only - arrays are rejected 400 server-side. */
227
227
  upsert(data: T): Promise<QueryResult & { data: T[] }>;
228
228
  delete(): QueryBuilder<T>;
229
229
 
@@ -311,7 +311,7 @@ interface StorageBucket {
311
311
  download(path: string): Promise<QueryResult & { data: Blob }>;
312
312
  list(path?: string, options?: ListOptions): Promise<QueryResult & { data: FileObject[] }>;
313
313
  remove(paths: string[]): Promise<QueryResult>;
314
- /** Same-origin SESSION url (not public GET needs the caller's cookie). Segments encoded. Outsiders: createSignedUrl. */
314
+ /** Same-origin SESSION url (not public - GET needs the caller's cookie). Segments encoded. Outsiders: createSignedUrl. */
315
315
  getPublicUrl(path: string): string;
316
316
  /** Real HMAC-signed expiring link (server-enforced, ≤7d). Anonymous fetches OK until exp. */
317
317
  createSignedUrl(path: string, expiresIn?: number): Promise<QueryResult & { data: { signedUrl: string } }>;
@@ -19,7 +19,7 @@
19
19
  * // Storage
20
20
  * await db.storage.from('avatars').upload('user1.jpg', file)
21
21
  *
22
- * // Storage with client-side compression (images shrink, text gzips
22
+ * // Storage with client-side compression (images shrink, text gzips -
23
23
  * * fewer bytes leave the device over the same chunked upload)
24
24
  * await db.storage.from('avatars').upload('user1.jpg', file, {
25
25
  * compress: { mode: 'auto', maxWidth: 1920, quality: 0.8, format: 'webp' },
@@ -337,7 +337,7 @@ class QueryBuilder {
337
337
  }
338
338
 
339
339
  /**
340
- * Upsert data (insert or update) single object only (D18: arrays are
340
+ * Upsert data (insert or update) - single object only (D18: arrays are
341
341
  * rejected 400 server-side; bulk upsert is insert-then-update instead).
342
342
  * @param {Object} data - Data to upsert
343
343
  * @returns {Promise<QueryResult>}
@@ -727,10 +727,10 @@ class AuthClient {
727
727
  * @param {Object} params
728
728
  * @param {string} params.email - Email address
729
729
  * @param {string} params.password - Password
730
- * @param {Object} [options] - { browser=true } browser:false returns the raw
730
+ * @param {Object} [options] - { browser=true } - browser:false returns the raw
731
731
  * token in `data.token` (no cookie set) for non-web clients / backends.
732
732
  * Store or forward it yourself. WARNING: a raw token in JS memory or
733
- * localStorage is readable by any script on the page prefer the cookie
733
+ * localStorage is readable by any script on the page - prefer the cookie
734
734
  * flow in browsers, and never use browser:false on shared machines.
735
735
  * @returns {Promise<QueryResult>}
736
736
  */
@@ -749,7 +749,7 @@ class AuthClient {
749
749
  * @param {Object} params
750
750
  * @param {string} params.email - Email address
751
751
  * @param {string} params.password - Password
752
- * @param {Object} [options] - { browser=true } browser:false returns the raw
752
+ * @param {Object} [options] - { browser=true } - browser:false returns the raw
753
753
  * token in `data.token` (no cookie set) for non-web clients / backends.
754
754
  * @returns {Promise<QueryResult>}
755
755
  */
@@ -785,7 +785,7 @@ class AuthClient {
785
785
  * Sign in with OAuth provider (Supabase-shaped, Fenaura transport).
786
786
  * Web (default): redirects the browser to the provider; session returns as
787
787
  * HttpOnly fenaura_eusid_* cookie (+ one-time ?code= for exchange).
788
- * Non-web ({ browser:false }): no navigation returns { data:{url} };
788
+ * Non-web ({ browser:false }): no navigation - returns { data:{url} };
789
789
  * open the URL in a system browser, then call exchangeCodeForSession(code)
790
790
  * with the ?code= your redirect_to (deep link) receives.
791
791
  * @param {string} provider - google|github|microsoft|apple|facebook|twitter|discord|linkedin|spotify|slack|gitlab|twitch
@@ -803,7 +803,7 @@ class AuthClient {
803
803
  if (redirectTo) q.set('redirect_to', redirectTo);
804
804
  if (scopes) q.set('scopes', scopes);
805
805
  if (queryParams && Object.keys(queryParams).length > 0) {
806
- // Global AJV removeAdditional strips unknown query keys (server.ts)
806
+ // Global AJV removeAdditional strips unknown query keys (server.ts) -
807
807
  // send extras as one JSON blob `qp`, server filters reserved keys.
808
808
  q.set('qp', JSON.stringify(queryParams));
809
809
  }
@@ -818,7 +818,7 @@ class AuthClient {
818
818
  /**
819
819
  * Exchange a one-time OAuth ?code= for the session (plan §12).
820
820
  * Web via /fenaura proxy: ALSO sets the first-party HttpOnly cookie.
821
- * Non-web / backend: use `data.token` store or forward it yourself
821
+ * Non-web / backend: use `data.token` - store or forward it yourself
822
822
  * (e.g. your backend sets it as a cookie for YOUR end users).
823
823
  * Codes are single-use, 60s TTL; replay → INVALID_CODE.
824
824
  * @param {string} code - one-time code from the callback redirect (?code=)
@@ -856,7 +856,7 @@ class AuthClient {
856
856
 
857
857
  /**
858
858
  * Send a password-reset code (account must exist; unknown addresses get a
859
- * generic success without email anti-enumeration).
859
+ * generic success without email - anti-enumeration).
860
860
  * @param {Object} params - { email }
861
861
  * @returns {Promise<QueryResult>} { data: { code_sent, email }, error }
862
862
  */
@@ -876,7 +876,7 @@ class AuthClient {
876
876
  }
877
877
 
878
878
  /**
879
- * Send a magic-link (OTP) code passwordless sign-in for existing accounts.
879
+ * Send a magic-link (OTP) code - passwordless sign-in for existing accounts.
880
880
  * @param {Object} params - { email }
881
881
  * @returns {Promise<QueryResult>} { data: { code_sent, email }, error }
882
882
  */
@@ -1025,7 +1025,7 @@ class StorageClient {
1025
1025
  * Upload a file in 1 MiB chunks, with live backend progress.
1026
1026
  * Flow: init → PUT each chunk (server answers received/total per
1027
1027
  * chunk) → complete (server assembles + sniffs content). A failed
1028
- * chunk is retried alone never the whole file.
1028
+ * chunk is retried alone - never the whole file.
1029
1029
  * @param {string} path - File path
1030
1030
  * @param {File|Blob} file - File to upload
1031
1031
  * @param {Object} [options] - Upload options
@@ -1045,7 +1045,7 @@ class StorageClient {
1045
1045
  if (!isPathSafe(path)) {
1046
1046
  throw new Error('Invalid path: directory traversal not allowed');
1047
1047
  }
1048
- // 0. compress on-device; only the smaller bytes leave the device
1048
+ // 0. compress - on-device; only the smaller bytes leave the device
1049
1049
  let source = file;
1050
1050
  let compression = null;
1051
1051
  if (options.compress !== undefined && options.compress !== false && options.compress !== null) {
@@ -1067,7 +1067,7 @@ class StorageClient {
1067
1067
  const creds = isProxy ? { credentials: 'include' } : {};
1068
1068
  const fail = (code, message, request_id = '') => ({ status: 'error', data: null, error: { code, message }, request_id });
1069
1069
 
1070
- // 1. init server validates name/type/size and hands back an upload id
1070
+ // 1. init - server validates name/type/size and hands back an upload id
1071
1071
  const initRes = await fetch(`${this._url}/api/v1/blob/${this._apiKey}/init`, {
1072
1072
  method: 'POST',
1073
1073
  headers: { 'Content-Type': 'application/json', ...authHeaders },
@@ -1093,7 +1093,7 @@ class StorageClient {
1093
1093
  }
1094
1094
  };
1095
1095
 
1096
- // 2. chunks sequential 1 MiB PUTs; each response is the progress event
1096
+ // 2. chunks - sequential 1 MiB PUTs; each response is the progress event
1097
1097
  for (let i = 0; i < chunks_total; i++) {
1098
1098
  const start = i * chunk_size;
1099
1099
  const end = Math.min(start + chunk_size, source.size);
@@ -1117,7 +1117,7 @@ class StorageClient {
1117
1117
  }
1118
1118
  }
1119
1119
 
1120
- // 3. complete server assembles, verifies size + content, stores
1120
+ // 3. complete - server assembles, verifies size + content, stores
1121
1121
  const doneRes = await fetch(`${this._url}/api/v1/blob/${this._apiKey}/complete/${upload_id}`, {
1122
1122
  method: 'POST',
1123
1123
  headers: { 'Content-Type': 'application/json', ...authHeaders },
@@ -1195,7 +1195,7 @@ class StorageClient {
1195
1195
  },
1196
1196
 
1197
1197
  /**
1198
- * Same-origin session URL (B15: NOT public despite the name the GET
1198
+ * Same-origin session URL (B15: NOT public despite the name - the GET
1199
1199
  * still requires the caller's session cookie; outsiders get 401. For
1200
1200
  * sharing with outsiders use createSignedUrl, which mints a real
1201
1201
  * HMAC-signed expiring link server-side).
@@ -1265,10 +1265,10 @@ class StorageClient {
1265
1265
  // Confirmed platform facts (researched 2026-09):
1266
1266
  // - CompressionStream gzip/deflate: Baseline, all browsers since May 2023.
1267
1267
  // - canvas.toBlob: PNG guaranteed; jpeg/webp widely supported BUT Safari may
1268
- // silently fall back to PNG when webp encode is missing so we NEVER trust
1268
+ // silently fall back to PNG when webp encode is missing - so we NEVER trust
1269
1269
  // the requested mime; the reported contentType is always out.type.
1270
1270
  // - Video transcode (360p) needs WebCodecs + a muxer lib (WebCodecs emits raw
1271
- // packets, not a playable file) intentionally NOT in core; see Phase 2.
1271
+ // packets, not a playable file) - intentionally NOT in core; see Phase 2.
1272
1272
 
1273
1273
  const COMPRESS_MODES = new Set(['auto', 'image', 'gzip', 'deflate', 'deflate-raw', 'none']);
1274
1274
  const COMPRESS_IMAGE_FORMATS = new Set(['original', 'webp', 'jpeg', 'jpg', 'png']);
@@ -1356,7 +1356,7 @@ function requireBlob(file) {
1356
1356
  // Image path: decode → scale-to-fit (aspect preserved, never upscale) →
1357
1357
  // re-encode. Lossless (`lossless:true`) forces PNG. JPEG gets a white
1358
1358
  // backdrop (no alpha channel). Anti-corruption: decode/encode failures throw
1359
- // and the caller keeps the original we never return partial bytes.
1359
+ // and the caller keeps the original - we never return partial bytes.
1360
1360
  async function compressImageBlob(blob, o) {
1361
1361
  if (!canCompressImage()) {
1362
1362
  throw new Error('Image compression needs a browser (canvas/createImageBitmap unavailable)');
@@ -1366,7 +1366,7 @@ async function compressImageBlob(blob, o) {
1366
1366
  try {
1367
1367
  bitmap = await createImageBitmap(blob);
1368
1368
  } catch {
1369
- throw new Error('Could not decode image (unsupported or corrupt file original kept)');
1369
+ throw new Error('Could not decode image (unsupported or corrupt file - original kept)');
1370
1370
  }
1371
1371
  try {
1372
1372
  throwIfAborted(o.signal);
@@ -1377,7 +1377,7 @@ async function compressImageBlob(blob, o) {
1377
1377
  canvas.width = w;
1378
1378
  canvas.height = h;
1379
1379
  const ctx = canvas.getContext('2d');
1380
- if (!ctx) throw new Error('Canvas 2D unavailable original kept');
1380
+ if (!ctx) throw new Error('Canvas 2D unavailable - original kept');
1381
1381
  let mime;
1382
1382
  if (o.lossless) mime = 'image/png';
1383
1383
  else if (o.format === 'original') mime = (blob.type && blob.type.startsWith('image/')) ? blob.type : 'image/jpeg';
@@ -1391,7 +1391,7 @@ async function compressImageBlob(blob, o) {
1391
1391
  const q = (o.lossless || mime === 'image/png') ? undefined : o.quality;
1392
1392
  const out = await new Promise((resolve, reject) => {
1393
1393
  try {
1394
- canvas.toBlob(b => (b ? resolve(b) : reject(new Error('Image encode failed original kept'))), mime, q);
1394
+ canvas.toBlob(b => (b ? resolve(b) : reject(new Error('Image encode failed - original kept'))), mime, q);
1395
1395
  } catch (err) {
1396
1396
  reject(err);
1397
1397
  }
@@ -1425,7 +1425,7 @@ async function compressGzipBlob(blob, format, signal) {
1425
1425
  total += value.byteLength;
1426
1426
  if (total > COMPRESS_MAX_BYTES) {
1427
1427
  try { await reader.cancel(); } catch { /* noop */ }
1428
- throw new Error('Compressed output exceeds 500 MB cap original kept');
1428
+ throw new Error('Compressed output exceeds 500 MB cap - original kept');
1429
1429
  }
1430
1430
  chunks.push(value);
1431
1431
  }
@@ -1436,7 +1436,7 @@ async function compressGzipBlob(blob, format, signal) {
1436
1436
  off += c.byteLength;
1437
1437
  }
1438
1438
  const out = new Blob([merged], { type: 'application/gzip' });
1439
- if (!out.size) throw new Error('Compression produced empty output original kept');
1439
+ if (!out.size) throw new Error('Compression produced empty output - original kept');
1440
1440
  return out;
1441
1441
  }
1442
1442
 
@@ -1450,7 +1450,7 @@ async function compressGzipBlob(blob, format, signal) {
1450
1450
  * preserved, never upscales), quality (0,1] default 0.8 (jpeg/webp only),
1451
1451
  * format original|webp|jpeg|png, lossless:true forces PNG.
1452
1452
  * - 'gzip'|'deflate'|'deflate-raw': lossless, any file type. Output is
1453
- * `application/gzip` decompress after download.
1453
+ * `application/gzip` - decompress after download.
1454
1454
  * - 'none': passthrough (no-op, still validates).
1455
1455
  *
1456
1456
  * Never corrupts: any decode/encode/stream failure throws and the original
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fenaura/sdk",
3
- "version": "0.2.2",
4
- "description": "Fenaura Client SDK direct database access, auth, and storage from the browser",
3
+ "version": "0.2.3",
4
+ "description": "Fenaura Client SDK - direct database access, auth, and storage from the browser",
5
5
  "main": "js/fenaura-client.js",
6
6
  "types": "js/fenaura-client.d.ts",
7
7
  "files": [