local-lite 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +5 -1
  2. package/dist/actions/store.js +1 -1
  3. package/dist/auth/index.d.ts +1 -0
  4. package/dist/auth/index.js +1 -0
  5. package/dist/auth/session.d.ts +107 -0
  6. package/dist/auth/session.js +259 -0
  7. package/dist/core/client.d.ts +18 -0
  8. package/dist/core/client.js +55 -2
  9. package/dist/index.d.ts +5 -0
  10. package/dist/index.js +5 -0
  11. package/dist/keys/generator.d.ts +38 -0
  12. package/dist/keys/generator.js +136 -0
  13. package/dist/keys/index.d.ts +5 -0
  14. package/dist/keys/index.js +21 -0
  15. package/dist/keys/manager.d.ts +59 -0
  16. package/dist/keys/manager.js +127 -0
  17. package/dist/keys/signed-token.d.ts +18 -0
  18. package/dist/keys/signed-token.js +171 -0
  19. package/dist/keys/store.d.ts +23 -0
  20. package/dist/keys/store.js +109 -0
  21. package/dist/keys/types.d.ts +95 -0
  22. package/dist/keys/types.js +2 -0
  23. package/dist/kv/index.d.ts +3 -0
  24. package/dist/kv/index.js +19 -0
  25. package/dist/kv/manager.d.ts +52 -0
  26. package/dist/kv/manager.js +164 -0
  27. package/dist/kv/store.d.ts +13 -0
  28. package/dist/kv/store.js +85 -0
  29. package/dist/kv/types.d.ts +27 -0
  30. package/dist/kv/types.js +2 -0
  31. package/dist/react/index.d.ts +3 -0
  32. package/dist/react/index.js +3 -0
  33. package/dist/react/kv.d.ts +9 -0
  34. package/dist/react/kv.js +53 -0
  35. package/dist/react/search.d.ts +19 -0
  36. package/dist/react/search.js +49 -0
  37. package/dist/react/session.d.ts +26 -0
  38. package/dist/react/session.js +118 -0
  39. package/dist/reactive/event-emitter.d.ts +1 -0
  40. package/dist/reactive/event-emitter.js +9 -6
  41. package/dist/scheduler/cron.d.ts +6 -0
  42. package/dist/scheduler/cron.js +62 -0
  43. package/dist/scheduler/index.d.ts +4 -0
  44. package/dist/scheduler/index.js +20 -0
  45. package/dist/scheduler/manager.d.ts +59 -0
  46. package/dist/scheduler/manager.js +186 -0
  47. package/dist/scheduler/store.d.ts +20 -0
  48. package/dist/scheduler/store.js +134 -0
  49. package/dist/scheduler/types.d.ts +44 -0
  50. package/dist/scheduler/types.js +2 -0
  51. package/dist/search/index.d.ts +3 -0
  52. package/dist/search/index.js +19 -0
  53. package/dist/search/manager.d.ts +42 -0
  54. package/dist/search/manager.js +123 -0
  55. package/dist/search/store.d.ts +33 -0
  56. package/dist/search/store.js +137 -0
  57. package/dist/search/types.d.ts +49 -0
  58. package/dist/search/types.js +2 -0
  59. package/dist/storage/driver.d.ts +25 -0
  60. package/dist/storage/driver.js +92 -0
  61. package/dist/storage/index.d.ts +4 -0
  62. package/dist/storage/index.js +20 -0
  63. package/dist/storage/manager.d.ts +48 -0
  64. package/dist/storage/manager.js +149 -0
  65. package/dist/storage/store.d.ts +14 -0
  66. package/dist/storage/store.js +116 -0
  67. package/dist/storage/types.d.ts +44 -0
  68. package/dist/storage/types.js +2 -0
  69. package/package.json +26 -1
@@ -0,0 +1,95 @@
1
+ export interface GenerateApiKeyOptions {
2
+ /**
3
+ * Prefix for the API key (e.g. 'prop', 'sk_live', 'pk_test').
4
+ * Default: 'key'
5
+ */
6
+ prefix?: string;
7
+ /**
8
+ * Number of random entropy bytes (higher is more secure).
9
+ * Default: 24 bytes (192 bits of entropy)
10
+ */
11
+ byteLength?: number;
12
+ /**
13
+ * Whether to embed a 4-byte checksum at the end of the key for instant validation.
14
+ * Default: true
15
+ */
16
+ withChecksum?: boolean;
17
+ /**
18
+ * Human-readable label or description for this key.
19
+ */
20
+ name?: string;
21
+ /**
22
+ * Arbitrary metadata (e.g. { businessId, scopes, ownerId }).
23
+ */
24
+ metadata?: Record<string, any>;
25
+ /**
26
+ * Expiration window in milliseconds from creation time.
27
+ */
28
+ expiresInMs?: number;
29
+ /**
30
+ * Explicit expiration timestamp in milliseconds.
31
+ */
32
+ expiresAt?: number;
33
+ }
34
+ export interface GeneratedApiKey {
35
+ /**
36
+ * The raw plaintext key string to return to the user (only shown once!).
37
+ * e.g. "prop_a7F9x..._c4b2"
38
+ */
39
+ key: string;
40
+ /**
41
+ * Cryptographic SHA-256 hash of the key for secure database storage.
42
+ */
43
+ keyHash: string;
44
+ /**
45
+ * Key prefix.
46
+ */
47
+ prefix: string;
48
+ /**
49
+ * Masked display string safe to show in UI (e.g. "prop_...c4b2").
50
+ */
51
+ display: string;
52
+ }
53
+ export interface ApiKeyRecord {
54
+ id: string;
55
+ name: string;
56
+ prefix: string;
57
+ display: string;
58
+ keyHash: string;
59
+ metadata?: Record<string, any>;
60
+ createdAt: number;
61
+ expiresAt?: number | null;
62
+ lastUsedAt?: number | null;
63
+ revoked: boolean;
64
+ revokedAt?: number | null;
65
+ }
66
+ export type KeyVerificationStatus = 'VALID' | 'INVALID' | 'EXPIRED' | 'REVOKED' | 'MALFORMED';
67
+ export interface VerifyStoredKeyResult {
68
+ valid: boolean;
69
+ status: KeyVerificationStatus;
70
+ record?: ApiKeyRecord;
71
+ error?: string;
72
+ }
73
+ export interface CreateSignedTokenOptions<T = Record<string, any>> {
74
+ payload: T;
75
+ secret: string;
76
+ expiresInMs?: number;
77
+ expiresAt?: number;
78
+ tokenId?: string;
79
+ }
80
+ export interface VerifySignedTokenResult<T = Record<string, any>> {
81
+ valid: boolean;
82
+ expired?: boolean;
83
+ payload?: T;
84
+ expiresAt?: number | null;
85
+ tokenId?: string;
86
+ error?: string;
87
+ }
88
+ export interface RenewSignedTokenOptions {
89
+ extendMs?: number;
90
+ newExpiresAt?: number;
91
+ secret?: string;
92
+ }
93
+ export interface ApiKeyManagerOptions {
94
+ tableName?: string;
95
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './store.js';
3
+ export * from './manager.js';
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./store.js"), exports);
19
+ __exportStar(require("./manager.js"), exports);
@@ -0,0 +1,52 @@
1
+ import { DBExecutable } from '../core/schema-manager.js';
2
+ import { EventEmitter } from '../reactive/event-emitter.js';
3
+ import { KVRecord, KVSetOptions, KVSetSecretOptions, KVStoreOptions } from './types.js';
4
+ export declare class KVManager {
5
+ private db;
6
+ private tableName;
7
+ private events?;
8
+ constructor(db: DBExecutable, eventsOrOptions?: EventEmitter | KVStoreOptions, options?: KVStoreOptions);
9
+ initialize(): Promise<void>;
10
+ /**
11
+ * Sets a value in the key-value store with optional TTL.
12
+ * Emits an event on the event emitter if configured.
13
+ */
14
+ set(key: string, value: any, options?: KVSetOptions): Promise<void>;
15
+ /**
16
+ * Retrieves the value for a key, returning defaultValue if not found or expired.
17
+ */
18
+ get<T = any>(key: string, defaultValue?: T): Promise<T | undefined>;
19
+ /**
20
+ * Retrieves the full KVRecord metadata for a key.
21
+ */
22
+ getRecord(key: string): Promise<KVRecord | null>;
23
+ /**
24
+ * Checks whether a non-expired key exists in the store.
25
+ */
26
+ has(key: string): Promise<boolean>;
27
+ /**
28
+ * Removes a key from the store.
29
+ * Emits an event on the event emitter if configured.
30
+ */
31
+ delete(key: string): Promise<void>;
32
+ /**
33
+ * Lists all non-expired keys, optionally filtered by a prefix.
34
+ */
35
+ keys(prefix?: string): Promise<string[]>;
36
+ /**
37
+ * Clears all stored key-value pairs.
38
+ */
39
+ clear(): Promise<void>;
40
+ /**
41
+ * Stores an encrypted secret using AES-256-GCM + PBKDF2 passphrase encryption.
42
+ */
43
+ setSecret(key: string, secretValue: any, options: KVSetSecretOptions): Promise<void>;
44
+ /**
45
+ * Decrypts and retrieves a stored secret.
46
+ */
47
+ getSecret<T = any>(key: string, passphrase: string, defaultValue?: T): Promise<T | undefined>;
48
+ /**
49
+ * Purges all expired keys immediately.
50
+ */
51
+ pruneExpired(): Promise<number>;
52
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KVManager = void 0;
4
+ const encryption_js_1 = require("../backups/encryption.js");
5
+ const signed_token_js_1 = require("../keys/signed-token.js");
6
+ const store_js_1 = require("./store.js");
7
+ class KVManager {
8
+ db;
9
+ tableName;
10
+ events;
11
+ constructor(db, eventsOrOptions, options) {
12
+ this.db = db;
13
+ let opts = {};
14
+ if (eventsOrOptions && typeof eventsOrOptions.emit === 'function') {
15
+ this.events = eventsOrOptions;
16
+ opts = options || {};
17
+ }
18
+ else if (eventsOrOptions) {
19
+ opts = eventsOrOptions;
20
+ }
21
+ this.tableName = opts.tableName ?? store_js_1.DEFAULT_KV_TABLE;
22
+ }
23
+ async initialize() {
24
+ await store_js_1.KVStore.ensureSchema(this.db, this.tableName);
25
+ }
26
+ /**
27
+ * Sets a value in the key-value store with optional TTL.
28
+ * Emits an event on the event emitter if configured.
29
+ */
30
+ async set(key, value, options = {}) {
31
+ const now = Date.now();
32
+ const expiresAt = options.expiresAt !== undefined
33
+ ? options.expiresAt
34
+ : options.ttlMs !== undefined
35
+ ? now + options.ttlMs
36
+ : null;
37
+ let type;
38
+ let valueStr;
39
+ if (typeof value === 'string') {
40
+ type = 'string';
41
+ valueStr = value;
42
+ }
43
+ else if (typeof value === 'number') {
44
+ type = 'number';
45
+ valueStr = String(value);
46
+ }
47
+ else if (typeof value === 'boolean') {
48
+ type = 'boolean';
49
+ valueStr = value ? '1' : '0';
50
+ }
51
+ else {
52
+ type = 'json';
53
+ valueStr = JSON.stringify(value ?? null);
54
+ }
55
+ await store_js_1.KVStore.set(this.db, this.tableName, key, valueStr, type, now, expiresAt);
56
+ this.events?.emit(this.tableName, { operation: 'SET', key, value });
57
+ }
58
+ /**
59
+ * Retrieves the value for a key, returning defaultValue if not found or expired.
60
+ */
61
+ async get(key, defaultValue) {
62
+ const record = await store_js_1.KVStore.get(this.db, this.tableName, key);
63
+ if (!record)
64
+ return defaultValue;
65
+ if (record.expiresAt && record.expiresAt <= Date.now()) {
66
+ // Lazy deletion of expired key
67
+ store_js_1.KVStore.delete(this.db, this.tableName, key).catch(() => { });
68
+ return defaultValue;
69
+ }
70
+ return record.value;
71
+ }
72
+ /**
73
+ * Retrieves the full KVRecord metadata for a key.
74
+ */
75
+ async getRecord(key) {
76
+ const record = await store_js_1.KVStore.get(this.db, this.tableName, key);
77
+ if (!record)
78
+ return null;
79
+ if (record.expiresAt && record.expiresAt <= Date.now()) {
80
+ store_js_1.KVStore.delete(this.db, this.tableName, key).catch(() => { });
81
+ return null;
82
+ }
83
+ return record;
84
+ }
85
+ /**
86
+ * Checks whether a non-expired key exists in the store.
87
+ */
88
+ async has(key) {
89
+ const val = await this.get(key);
90
+ return val !== undefined;
91
+ }
92
+ /**
93
+ * Removes a key from the store.
94
+ * Emits an event on the event emitter if configured.
95
+ */
96
+ async delete(key) {
97
+ await store_js_1.KVStore.delete(this.db, this.tableName, key);
98
+ this.events?.emit(this.tableName, { operation: 'DELETE', key });
99
+ }
100
+ /**
101
+ * Lists all non-expired keys, optionally filtered by a prefix.
102
+ */
103
+ async keys(prefix) {
104
+ return store_js_1.KVStore.listKeys(this.db, this.tableName, prefix);
105
+ }
106
+ /**
107
+ * Clears all stored key-value pairs.
108
+ */
109
+ async clear() {
110
+ await store_js_1.KVStore.clear(this.db, this.tableName);
111
+ this.events?.emit(this.tableName, { operation: 'CLEAR' });
112
+ }
113
+ /**
114
+ * Stores an encrypted secret using AES-256-GCM + PBKDF2 passphrase encryption.
115
+ */
116
+ async setSecret(key, secretValue, options) {
117
+ if (!options.passphrase) {
118
+ throw new Error('Passphrase is required to store encrypted secret.');
119
+ }
120
+ const strValue = typeof secretValue === 'string' ? secretValue : JSON.stringify(secretValue);
121
+ const plainBytes = new TextEncoder().encode(strValue);
122
+ const encryptedBytes = await (0, encryption_js_1.encryptBackupData)(plainBytes, options.passphrase);
123
+ const encryptedBase64 = (0, signed_token_js_1.base64UrlEncode)(encryptedBytes);
124
+ const now = Date.now();
125
+ const expiresAt = options.expiresAt !== undefined
126
+ ? options.expiresAt
127
+ : options.ttlMs !== undefined
128
+ ? now + options.ttlMs
129
+ : null;
130
+ await store_js_1.KVStore.set(this.db, this.tableName, key, encryptedBase64, 'encrypted', now, expiresAt);
131
+ this.events?.emit(this.tableName, { operation: 'SET_SECRET', key });
132
+ }
133
+ /**
134
+ * Decrypts and retrieves a stored secret.
135
+ */
136
+ async getSecret(key, passphrase, defaultValue) {
137
+ const record = await store_js_1.KVStore.get(this.db, this.tableName, key);
138
+ if (!record)
139
+ return defaultValue;
140
+ if (record.expiresAt && record.expiresAt <= Date.now()) {
141
+ store_js_1.KVStore.delete(this.db, this.tableName, key).catch(() => { });
142
+ return defaultValue;
143
+ }
144
+ if (record.type !== 'encrypted') {
145
+ return record.value;
146
+ }
147
+ const encryptedBytes = (0, signed_token_js_1.base64UrlDecode)(record.value);
148
+ const decryptedBytes = await (0, encryption_js_1.decryptBackupData)(encryptedBytes, passphrase);
149
+ const text = new TextDecoder().decode(decryptedBytes);
150
+ try {
151
+ return JSON.parse(text);
152
+ }
153
+ catch {
154
+ return text;
155
+ }
156
+ }
157
+ /**
158
+ * Purges all expired keys immediately.
159
+ */
160
+ async pruneExpired() {
161
+ return store_js_1.KVStore.pruneExpired(this.db, this.tableName);
162
+ }
163
+ }
164
+ exports.KVManager = KVManager;
@@ -0,0 +1,13 @@
1
+ import { DBExecutable } from '../core/schema-manager.js';
2
+ import { KVRecord } from './types.js';
3
+ export declare const DEFAULT_KV_TABLE = "_local_kv";
4
+ export declare function mapKVRow(r: any): KVRecord;
5
+ export declare class KVStore {
6
+ static ensureSchema(db: DBExecutable, tableName?: string): Promise<void>;
7
+ static set(db: DBExecutable, tableName: string | undefined, key: string, valueStr: string, type: string, now: number, expiresAt: number | null): Promise<void>;
8
+ static get(db: DBExecutable, tableName: string | undefined, key: string): Promise<KVRecord | null>;
9
+ static delete(db: DBExecutable, tableName: string | undefined, key: string): Promise<boolean>;
10
+ static listKeys(db: DBExecutable, tableName?: string, prefix?: string): Promise<string[]>;
11
+ static clear(db: DBExecutable, tableName?: string): Promise<void>;
12
+ static pruneExpired(db: DBExecutable, tableName?: string): Promise<number>;
13
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KVStore = exports.DEFAULT_KV_TABLE = void 0;
4
+ exports.mapKVRow = mapKVRow;
5
+ exports.DEFAULT_KV_TABLE = '_local_kv';
6
+ function mapKVRow(r) {
7
+ let val = r.value;
8
+ const type = r.type;
9
+ if (type === 'number') {
10
+ val = Number(val);
11
+ }
12
+ else if (type === 'boolean') {
13
+ val = val === 'true' || val === '1' || val === 1;
14
+ }
15
+ else if (type === 'json') {
16
+ try {
17
+ val = JSON.parse(val);
18
+ }
19
+ catch {
20
+ // fallback
21
+ }
22
+ }
23
+ return {
24
+ key: r.key,
25
+ value: val,
26
+ type: r.type,
27
+ createdAt: Number(r.created_at || 0),
28
+ updatedAt: Number(r.updated_at || 0),
29
+ expiresAt: r.expires_at != null ? Number(r.expires_at) : null,
30
+ };
31
+ }
32
+ class KVStore {
33
+ static async ensureSchema(db, tableName = exports.DEFAULT_KV_TABLE) {
34
+ await db.execute(`CREATE TABLE IF NOT EXISTS ${tableName} (
35
+ key TEXT PRIMARY KEY,
36
+ value TEXT NOT NULL,
37
+ type TEXT NOT NULL,
38
+ created_at INTEGER NOT NULL,
39
+ updated_at INTEGER NOT NULL,
40
+ expires_at INTEGER
41
+ );`);
42
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_${tableName}_expires ON ${tableName} (expires_at);`);
43
+ }
44
+ static async set(db, tableName = exports.DEFAULT_KV_TABLE, key, valueStr, type, now, expiresAt) {
45
+ await db.execute(`INSERT INTO ${tableName} (key, value, type, created_at, updated_at, expires_at)
46
+ VALUES (?, ?, ?, ?, ?, ?)
47
+ ON CONFLICT(key) DO UPDATE SET
48
+ value = excluded.value,
49
+ type = excluded.type,
50
+ updated_at = excluded.updated_at,
51
+ expires_at = excluded.expires_at;`, [key, valueStr, type, now, now, expiresAt]);
52
+ }
53
+ static async get(db, tableName = exports.DEFAULT_KV_TABLE, key) {
54
+ const rows = await db.execute(`SELECT * FROM ${tableName} WHERE key = ? LIMIT 1;`, [key]);
55
+ if (!rows || rows.length === 0)
56
+ return null;
57
+ return mapKVRow(rows[0]);
58
+ }
59
+ static async delete(db, tableName = exports.DEFAULT_KV_TABLE, key) {
60
+ await db.execute(`DELETE FROM ${tableName} WHERE key = ?;`, [key]);
61
+ return true;
62
+ }
63
+ static async listKeys(db, tableName = exports.DEFAULT_KV_TABLE, prefix) {
64
+ let rows;
65
+ if (prefix) {
66
+ rows = await db.execute(`SELECT key, expires_at FROM ${tableName} WHERE key LIKE ? ORDER BY key ASC;`, [`${prefix}%`]);
67
+ }
68
+ else {
69
+ rows = await db.execute(`SELECT key, expires_at FROM ${tableName} ORDER BY key ASC;`);
70
+ }
71
+ const now = Date.now();
72
+ return (rows || [])
73
+ .filter((r) => r.expires_at == null || Number(r.expires_at) > now)
74
+ .map((r) => r.key);
75
+ }
76
+ static async clear(db, tableName = exports.DEFAULT_KV_TABLE) {
77
+ await db.execute(`DELETE FROM ${tableName};`);
78
+ }
79
+ static async pruneExpired(db, tableName = exports.DEFAULT_KV_TABLE) {
80
+ const now = Date.now();
81
+ await db.execute(`DELETE FROM ${tableName} WHERE expires_at IS NOT NULL AND expires_at <= ?;`, [now]);
82
+ return 0;
83
+ }
84
+ }
85
+ exports.KVStore = KVStore;
@@ -0,0 +1,27 @@
1
+ export interface KVSetOptions {
2
+ /**
3
+ * Time-to-live in milliseconds from current time.
4
+ */
5
+ ttlMs?: number;
6
+ /**
7
+ * Explicit Unix timestamp in milliseconds when this key expires.
8
+ */
9
+ expiresAt?: number;
10
+ }
11
+ export interface KVSetSecretOptions extends KVSetOptions {
12
+ /**
13
+ * Passphrase used to encrypt the value using AES-256-GCM.
14
+ */
15
+ passphrase: string;
16
+ }
17
+ export interface KVRecord<T = any> {
18
+ key: string;
19
+ value: T;
20
+ type: 'string' | 'number' | 'boolean' | 'json' | 'encrypted';
21
+ createdAt: number;
22
+ updatedAt: number;
23
+ expiresAt?: number | null;
24
+ }
25
+ export interface KVStoreOptions {
26
+ tableName?: string;
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -131,3 +131,6 @@ export { SyncInspector } from './SyncInspector.js';
131
131
  export type { SyncInspectorProps } from './SyncInspector.js';
132
132
  export * from './auth.js';
133
133
  export * from './backups.js';
134
+ export * from './kv.js';
135
+ export * from './search.js';
136
+ export * from './session.js';
@@ -646,3 +646,6 @@ var SyncInspector_js_1 = require("./SyncInspector.js");
646
646
  Object.defineProperty(exports, "SyncInspector", { enumerable: true, get: function () { return SyncInspector_js_1.SyncInspector; } });
647
647
  __exportStar(require("./auth.js"), exports);
648
648
  __exportStar(require("./backups.js"), exports);
649
+ __exportStar(require("./kv.js"), exports);
650
+ __exportStar(require("./search.js"), exports);
651
+ __exportStar(require("./session.js"), exports);
@@ -0,0 +1,9 @@
1
+ import { LocalFirstClient } from '../core/client.js';
2
+ /**
3
+ * Reactive React hook for client.kv key-value preferences.
4
+ * Automatically re-renders whenever the key is updated or deleted.
5
+ *
6
+ * Example:
7
+ * const [theme, setTheme, loading] = useKV(client, 'theme', 'light');
8
+ */
9
+ export declare function useKV<T = any>(client: LocalFirstClient | null | undefined, key: string, defaultValue?: T): [T | undefined, (value: T) => Promise<void>, boolean];
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useKV = useKV;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Reactive React hook for client.kv key-value preferences.
7
+ * Automatically re-renders whenever the key is updated or deleted.
8
+ *
9
+ * Example:
10
+ * const [theme, setTheme, loading] = useKV(client, 'theme', 'light');
11
+ */
12
+ function useKV(client, key, defaultValue) {
13
+ const [value, setValueState] = (0, react_1.useState)(defaultValue);
14
+ const [loading, setLoading] = (0, react_1.useState)(true);
15
+ (0, react_1.useEffect)(() => {
16
+ if (!client) {
17
+ setValueState(defaultValue);
18
+ setLoading(false);
19
+ return;
20
+ }
21
+ let isMounted = true;
22
+ const load = async () => {
23
+ try {
24
+ const val = await client.kv.get(key, defaultValue);
25
+ if (isMounted) {
26
+ setValueState(val);
27
+ setLoading(false);
28
+ }
29
+ }
30
+ catch {
31
+ if (isMounted)
32
+ setLoading(false);
33
+ }
34
+ };
35
+ load();
36
+ const unsubscribe = client.events.on('_local_kv', (e) => {
37
+ if (!e || e.key === key || !e.key) {
38
+ load();
39
+ }
40
+ });
41
+ return () => {
42
+ isMounted = false;
43
+ unsubscribe();
44
+ };
45
+ }, [client, key, defaultValue]);
46
+ const setKVValue = (0, react_1.useCallback)(async (newValue) => {
47
+ if (!client)
48
+ return;
49
+ await client.kv.set(key, newValue);
50
+ setValueState(newValue);
51
+ }, [client, key]);
52
+ return [value, setKVValue, loading];
53
+ }
@@ -0,0 +1,19 @@
1
+ import { LocalFirstClient } from '../core/client.js';
2
+ import { SearchQueryOptions, SearchResult } from '../search/types.js';
3
+ export interface UseSearchOptions extends SearchQueryOptions {
4
+ /**
5
+ * Debounce delay in milliseconds (default: 150ms).
6
+ */
7
+ debounceMs?: number;
8
+ }
9
+ /**
10
+ * Reactive React hook for instant full-text search with automatic debouncing.
11
+ *
12
+ * Example:
13
+ * const { results, loading, error } = useSearch(client, 'properties', searchQuery);
14
+ */
15
+ export declare function useSearch<T = any>(client: LocalFirstClient | null | undefined, table: string, queryText: string, options?: UseSearchOptions): {
16
+ results: SearchResult<T>[];
17
+ loading: boolean;
18
+ error: Error | null;
19
+ };
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSearch = useSearch;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Reactive React hook for instant full-text search with automatic debouncing.
7
+ *
8
+ * Example:
9
+ * const { results, loading, error } = useSearch(client, 'properties', searchQuery);
10
+ */
11
+ function useSearch(client, table, queryText, options) {
12
+ const [results, setResults] = (0, react_1.useState)([]);
13
+ const [loading, setLoading] = (0, react_1.useState)(false);
14
+ const [error, setError] = (0, react_1.useState)(null);
15
+ const debounceMs = options?.debounceMs ?? 150;
16
+ (0, react_1.useEffect)(() => {
17
+ if (!client || !table || !queryText.trim()) {
18
+ setResults([]);
19
+ setLoading(false);
20
+ return;
21
+ }
22
+ let isMounted = true;
23
+ setLoading(true);
24
+ const timer = setTimeout(async () => {
25
+ try {
26
+ const res = await client.search.query(table, queryText, options);
27
+ if (isMounted) {
28
+ setResults(res);
29
+ setError(null);
30
+ }
31
+ }
32
+ catch (err) {
33
+ if (isMounted) {
34
+ setError(err instanceof Error ? err : new Error(String(err)));
35
+ }
36
+ }
37
+ finally {
38
+ if (isMounted) {
39
+ setLoading(false);
40
+ }
41
+ }
42
+ }, debounceMs);
43
+ return () => {
44
+ isMounted = false;
45
+ clearTimeout(timer);
46
+ };
47
+ }, [client, table, queryText, debounceMs]);
48
+ return { results, loading, error };
49
+ }
@@ -0,0 +1,26 @@
1
+ import { LocalFirstClient } from '../core/client.js';
2
+ import { SessionRecord, CreateSessionOptions } from '../auth/session.js';
3
+ export interface UseSessionResult<TUser = any> {
4
+ session: SessionRecord<TUser> | null;
5
+ user: TUser | null;
6
+ isAuthenticated: boolean;
7
+ isOffline: boolean;
8
+ loading: boolean;
9
+ createSession: (options: CreateSessionOptions<TUser>) => Promise<SessionRecord<TUser>>;
10
+ updateUser: (partialUser: Partial<TUser>) => Promise<SessionRecord<TUser> | null>;
11
+ updateTokens: (tokens: {
12
+ token?: string | null;
13
+ refreshToken?: string | null;
14
+ expiresAt?: number | null;
15
+ }) => Promise<void>;
16
+ signOut: () => Promise<void>;
17
+ refetchSession: () => Promise<SessionRecord<TUser> | null>;
18
+ }
19
+ /**
20
+ * Reactive React hook for persistent, offline-first user session management.
21
+ * The session remains authenticated even when offline, ensuring uninterrupted UX.
22
+ *
23
+ * Example:
24
+ * const { user, isAuthenticated, isOffline, signOut } = useSession(client);
25
+ */
26
+ export declare function useSession<TUser = any>(client: LocalFirstClient | null | undefined): UseSessionResult<TUser>;