@framers/sql-storage-adapter 0.6.16 → 0.7.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.
Files changed (72) hide show
  1. package/README.md +94 -3
  2. package/dist/adapters/betterSqliteAdapter.d.ts +18 -2
  3. package/dist/adapters/betterSqliteAdapter.d.ts.map +1 -1
  4. package/dist/adapters/betterSqliteAdapter.js +13 -5
  5. package/dist/adapters/betterSqliteAdapter.js.map +1 -1
  6. package/dist/adapters/capacitorSqliteAdapter.d.ts +8 -0
  7. package/dist/adapters/capacitorSqliteAdapter.d.ts.map +1 -1
  8. package/dist/adapters/capacitorSqliteAdapter.js +4 -1
  9. package/dist/adapters/capacitorSqliteAdapter.js.map +1 -1
  10. package/dist/adapters/electron/electronMainAdapter.d.ts +10 -0
  11. package/dist/adapters/electron/electronMainAdapter.d.ts.map +1 -1
  12. package/dist/adapters/electron/electronMainAdapter.js +21 -12
  13. package/dist/adapters/electron/electronMainAdapter.js.map +1 -1
  14. package/dist/adapters/indexedDbAdapter.d.ts +198 -54
  15. package/dist/adapters/indexedDbAdapter.d.ts.map +1 -1
  16. package/dist/adapters/indexedDbAdapter.js +549 -129
  17. package/dist/adapters/indexedDbAdapter.js.map +1 -1
  18. package/dist/adapters/postgresAdapter.d.ts +143 -4
  19. package/dist/adapters/postgresAdapter.d.ts.map +1 -1
  20. package/dist/adapters/postgresAdapter.js +612 -107
  21. package/dist/adapters/postgresAdapter.js.map +1 -1
  22. package/dist/adapters/sqlJsAdapter.d.ts +17 -3
  23. package/dist/adapters/sqlJsAdapter.d.ts.map +1 -1
  24. package/dist/adapters/sqlJsAdapter.js +15 -3
  25. package/dist/adapters/sqlJsAdapter.js.map +1 -1
  26. package/dist/core/database.d.ts +28 -8
  27. package/dist/core/database.d.ts.map +1 -1
  28. package/dist/core/database.js +34 -5
  29. package/dist/core/database.js.map +1 -1
  30. package/dist/core/resolver.d.ts +14 -1
  31. package/dist/core/resolver.d.ts.map +1 -1
  32. package/dist/core/resolver.js +28 -8
  33. package/dist/core/resolver.js.map +1 -1
  34. package/dist/features/kv/idb.d.ts +79 -0
  35. package/dist/features/kv/idb.d.ts.map +1 -0
  36. package/dist/features/kv/idb.js +14 -0
  37. package/dist/features/kv/idb.js.map +1 -0
  38. package/dist/features/kv/index.d.ts +14 -0
  39. package/dist/features/kv/index.d.ts.map +1 -0
  40. package/dist/features/kv/index.js +14 -0
  41. package/dist/features/kv/index.js.map +1 -0
  42. package/dist/features/kv/indexedDbKeyValueStore.d.ts +77 -0
  43. package/dist/features/kv/indexedDbKeyValueStore.d.ts.map +1 -0
  44. package/dist/features/kv/indexedDbKeyValueStore.js +127 -0
  45. package/dist/features/kv/indexedDbKeyValueStore.js.map +1 -0
  46. package/dist/features/kv/keyValueStore.d.ts +48 -0
  47. package/dist/features/kv/keyValueStore.d.ts.map +1 -0
  48. package/dist/features/kv/keyValueStore.js +54 -0
  49. package/dist/features/kv/keyValueStore.js.map +1 -0
  50. package/dist/features/kv/sqlKeyValueStore.d.ts +34 -0
  51. package/dist/features/kv/sqlKeyValueStore.d.ts.map +1 -0
  52. package/dist/features/kv/sqlKeyValueStore.js +91 -0
  53. package/dist/features/kv/sqlKeyValueStore.js.map +1 -0
  54. package/dist/features/kv/valueCodec.d.ts +35 -0
  55. package/dist/features/kv/valueCodec.d.ts.map +1 -0
  56. package/dist/features/kv/valueCodec.js +462 -0
  57. package/dist/features/kv/valueCodec.js.map +1 -0
  58. package/dist/features/migrations/dataExport.js +9 -6
  59. package/dist/features/migrations/dataExport.js.map +1 -1
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/shared/postgresTarget.d.ts +4 -3
  65. package/dist/shared/postgresTarget.d.ts.map +1 -1
  66. package/dist/shared/postgresTarget.js +6 -4
  67. package/dist/shared/postgresTarget.js.map +1 -1
  68. package/dist/shared/segment.d.ts +43 -0
  69. package/dist/shared/segment.d.ts.map +1 -0
  70. package/dist/shared/segment.js +77 -0
  71. package/dist/shared/segment.js.map +1 -0
  72. package/package.json +10 -2
@@ -1 +1 @@
1
- {"version":3,"file":"postgresTarget.d.ts","sourceRoot":"","sources":["../../src/shared/postgresTarget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,qCAAqC;AACrC,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,KAAK,IAAI,MAAuD,CAAC;AAE7G;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,YAAa,sBAAsB,GAAG,SAAS,KAAG,OAO/E,CAAC"}
1
+ {"version":3,"file":"postgresTarget.d.ts","sourceRoot":"","sources":["../../src/shared/postgresTarget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,qCAAqC;AACrC,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,KAAK,IAAI,MAAuD,CAAC;AAE7G;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,YAAa,sBAAsB,GAAG,SAAS,KAAG,OAQ/E,CAAC"}
@@ -1,12 +1,14 @@
1
1
  /** A string with something in it. */
2
2
  export const isNonEmpty = (value) => typeof value === 'string' && value.length > 0;
3
3
  /**
4
- * Whether Postgres options name a server: a connection string, or a host,
5
- * database or user. `{ connectionString: process.env.DATABASE_URL }` with
6
- * DATABASE_URL unset names none.
4
+ * Whether Postgres options name a server: a pool of the caller's, a
5
+ * connection string, or a host, database or user.
6
+ * `{ connectionString: process.env.DATABASE_URL }` with DATABASE_URL unset
7
+ * names none.
7
8
  */
8
9
  export const namesPostgresServer = (options) => Boolean(options &&
9
- (isNonEmpty(options.connectionString) ||
10
+ (Boolean(options.pool) ||
11
+ isNonEmpty(options.connectionString) ||
10
12
  isNonEmpty(options.host) ||
11
13
  isNonEmpty(options.database) ||
12
14
  isNonEmpty(options.user)));
@@ -1 +1 @@
1
- {"version":3,"file":"postgresTarget.js","sourceRoot":"","sources":["../../src/shared/postgresTarget.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AAE7G;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAA2C,EAAW,EAAE,CAC1F,OAAO,CACL,OAAO;IACL,CAAC,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACnC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;QACxB,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5B,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC9B,CAAC"}
1
+ {"version":3,"file":"postgresTarget.js","sourceRoot":"","sources":["../../src/shared/postgresTarget.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAc,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AAE7G;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAA2C,EAAW,EAAE,CAC1F,OAAO,CACL,OAAO;IACL,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QACpB,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACpC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC;QACxB,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5B,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAC9B,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Segments keep one user's tables apart from another's in a database they
3
+ * share. On PostgreSQL a segment is a schema of its own; on the SQLite family a
4
+ * database file of its own; on IndexedDB and Capacitor a database name of its
5
+ * own. A segment name is the same on every backend, so one name gives a host
6
+ * the same separation on the server, the desktop, the phone and the browser.
7
+ *
8
+ * This module imports nothing, so browser bundles can include it.
9
+ */
10
+ /**
11
+ * Checks a segment name and returns it.
12
+ *
13
+ * A segment name is lower-case letters, digits and underscores, starts with a
14
+ * letter or an underscore, and has at most 63 characters: an unquoted
15
+ * PostgreSQL identifier, and a safe file, IndexedDB and Capacitor database
16
+ * name. It is not `public`, `information_schema` or a name beginning with
17
+ * `pg_`, which PostgreSQL keeps for its own schemas.
18
+ *
19
+ * @throws {Error} When the name is not one a segment can take.
20
+ */
21
+ export declare const validateSegment: (segment: string) => string;
22
+ /**
23
+ * The name of a segment's database: `name` followed by `-` and the segment,
24
+ * or `name` itself when there is no segment. For IndexedDB and Capacitor
25
+ * database names: `app-db` and segment `agentos` give `app-db-agentos`.
26
+ *
27
+ * @throws {Error} When the segment is not one a segment name can take.
28
+ */
29
+ export declare const segmentName: (name: string, segment: string | undefined) => string;
30
+ /**
31
+ * The file of a segment's database: the segment goes between the file's name
32
+ * and its extension (`/data/app.sqlite3` and segment `agentos` give
33
+ * `/data/app-agentos.sqlite3`), or after a name with no extension. A `file:`
34
+ * URI keeps its query, and SQLite's in-memory names (`:memory:`,
35
+ * `file::memory:`) stay as they are, since each in-memory connection is a
36
+ * database of its own. Without a segment the path is returned as it is.
37
+ *
38
+ * @throws {Error} When the segment is not one a segment name can take.
39
+ */
40
+ export declare const segmentFilePath: (filePath: string, segment: string | undefined) => string;
41
+ /** Whether a path names an in-memory SQLite database, which no file holds. */
42
+ export declare const namesInMemoryDatabase: (filePath: string) => boolean;
43
+ //# sourceMappingURL=segment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../src/shared/segment.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAWH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,YAAa,MAAM,KAAG,MAajD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM,GAAG,SAAS,KAAG,MACF,CAAC;AAMvE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,aAAc,MAAM,WAAW,MAAM,GAAG,SAAS,KAAG,MAkB/E,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,aAlCA,MAAM,KAAG,OAkCQ,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Segments keep one user's tables apart from another's in a database they
3
+ * share. On PostgreSQL a segment is a schema of its own; on the SQLite family a
4
+ * database file of its own; on IndexedDB and Capacitor a database name of its
5
+ * own. A segment name is the same on every backend, so one name gives a host
6
+ * the same separation on the server, the desktop, the phone and the browser.
7
+ *
8
+ * This module imports nothing, so browser bundles can include it.
9
+ */
10
+ /** PostgreSQL's identifier limit: a schema name has at most 63 bytes. */
11
+ const MAX_SEGMENT_LENGTH = 63;
12
+ /** Lower-case letters, digits and underscores, starting with a letter or an underscore. */
13
+ const SEGMENT_PATTERN = /^[a-z_][a-z0-9_]*$/;
14
+ /** Schemas PostgreSQL keeps for itself, besides the names beginning with pg_. */
15
+ const RESERVED_SEGMENTS = new Set(['public', 'information_schema']);
16
+ /**
17
+ * Checks a segment name and returns it.
18
+ *
19
+ * A segment name is lower-case letters, digits and underscores, starts with a
20
+ * letter or an underscore, and has at most 63 characters: an unquoted
21
+ * PostgreSQL identifier, and a safe file, IndexedDB and Capacitor database
22
+ * name. It is not `public`, `information_schema` or a name beginning with
23
+ * `pg_`, which PostgreSQL keeps for its own schemas.
24
+ *
25
+ * @throws {Error} When the name is not one a segment can take.
26
+ */
27
+ export const validateSegment = (segment) => {
28
+ if (typeof segment !== 'string' || !SEGMENT_PATTERN.test(segment) || segment.length > MAX_SEGMENT_LENGTH) {
29
+ throw new Error(`Invalid segment ${JSON.stringify(segment)}: a segment name is lower-case letters, digits and underscores, ` +
30
+ `starts with a letter or an underscore, and has at most ${MAX_SEGMENT_LENGTH} characters.`);
31
+ }
32
+ if (RESERVED_SEGMENTS.has(segment) || segment.startsWith('pg_')) {
33
+ throw new Error(`Invalid segment "${segment}": PostgreSQL keeps public, information_schema and the names beginning with pg_ for its own schemas.`);
34
+ }
35
+ return segment;
36
+ };
37
+ /**
38
+ * The name of a segment's database: `name` followed by `-` and the segment,
39
+ * or `name` itself when there is no segment. For IndexedDB and Capacitor
40
+ * database names: `app-db` and segment `agentos` give `app-db-agentos`.
41
+ *
42
+ * @throws {Error} When the segment is not one a segment name can take.
43
+ */
44
+ export const segmentName = (name, segment) => segment === undefined ? name : `${name}-${validateSegment(segment)}`;
45
+ /** SQLite's names for a database that lives in its connection alone. */
46
+ const isInMemoryPath = (filePath) => filePath === '' || filePath === ':memory:' || filePath.startsWith('file::memory:');
47
+ /**
48
+ * The file of a segment's database: the segment goes between the file's name
49
+ * and its extension (`/data/app.sqlite3` and segment `agentos` give
50
+ * `/data/app-agentos.sqlite3`), or after a name with no extension. A `file:`
51
+ * URI keeps its query, and SQLite's in-memory names (`:memory:`,
52
+ * `file::memory:`) stay as they are, since each in-memory connection is a
53
+ * database of its own. Without a segment the path is returned as it is.
54
+ *
55
+ * @throws {Error} When the segment is not one a segment name can take.
56
+ */
57
+ export const segmentFilePath = (filePath, segment) => {
58
+ if (segment === undefined)
59
+ return filePath;
60
+ validateSegment(segment);
61
+ if (isInMemoryPath(filePath))
62
+ return filePath;
63
+ // A file: URI's path ends at its query or fragment.
64
+ const queryAt = filePath.startsWith('file:') ? filePath.search(/[?#]/) : -1;
65
+ const pathPart = queryAt === -1 ? filePath : filePath.slice(0, queryAt);
66
+ const rest = queryAt === -1 ? '' : filePath.slice(queryAt);
67
+ const nameAt = Math.max(pathPart.lastIndexOf('/'), pathPart.lastIndexOf('\\'), pathPart.startsWith('file:') ? 4 : -1) + 1;
68
+ const directory = pathPart.slice(0, nameAt);
69
+ const name = pathPart.slice(nameAt);
70
+ // A dot that begins the name (.store) starts no extension.
71
+ const extensionAt = name.lastIndexOf('.');
72
+ const segmented = extensionAt > 0 ? `${name.slice(0, extensionAt)}-${segment}${name.slice(extensionAt)}` : `${name}-${segment}`;
73
+ return `${directory}${segmented}${rest}`;
74
+ };
75
+ /** Whether a path names an in-memory SQLite database, which no file holds. */
76
+ export const namesInMemoryDatabase = isInMemoryPath;
77
+ //# sourceMappingURL=segment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment.js","sourceRoot":"","sources":["../../src/shared/segment.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@framers/sql-storage-adapter",
3
- "version": "0.6.16",
3
+ "version": "0.7.0",
4
4
  "description": "Robust cross-platform SQL storage abstraction with automatic fallbacks and runtime detection",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,6 +32,11 @@
32
32
  "import": "./dist/features/sync/index.js",
33
33
  "default": "./dist/features/sync/index.js"
34
34
  },
35
+ "./kv": {
36
+ "types": "./dist/features/kv/index.d.ts",
37
+ "import": "./dist/features/kv/index.js",
38
+ "default": "./dist/features/kv/index.js"
39
+ },
35
40
  "./package.json": "./package.json"
36
41
  },
37
42
  "files": [
@@ -48,6 +53,7 @@
48
53
  "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"",
49
54
  "typecheck": "tsc --noEmit",
50
55
  "test": "vitest run",
56
+ "test:browser": "vitest run --config vitest.browser.config.ts",
51
57
  "test:watch": "vitest",
52
58
  "test:coverage": "vitest run --coverage",
53
59
  "coverage": "npm run test:coverage",
@@ -121,7 +127,9 @@
121
127
  "typedoc": "^0.26.11",
122
128
  "typescript": "^5.6.3",
123
129
  "typescript-eslint": "^8.46.2",
124
- "vitest": "^3.2.7"
130
+ "vitest": "^3.2.7",
131
+ "@vitest/browser": "^3.2.7",
132
+ "playwright": "~1.63.0"
125
133
  },
126
134
  "dependencies": {
127
135
  "pg": "^8.13.1",