@fougere/adapter-duckdb 0.6.0-alpha.0 → 0.7.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/dist/index.d.ts CHANGED
@@ -1,26 +1,4 @@
1
- /**
2
- * Read across an app's sources — one SQL query over what can be attached.
3
- *
4
- * The by-key path (`findByKeys` and its dual) ENRICHES a page: it answers "I hold
5
- * these rows, give me the related ones". It cannot SELECT one — filtering, sorting,
6
- * paginating or counting on the other side collapses into reading that side whole.
7
- * That is the hole this closes, and it is not a reporting nicety: "my loans, newest
8
- * book first" crosses.
9
- *
10
- * What it is NOT is a query builder. A cross-source builder would be a Calcite in
11
- * TypeScript, and it would promise a composability the sources have not got. The query
12
- * stays SQL; what Fougere contributes is the three derivations a hand-written one would
13
- * duplicate — where each entity lives, what its table and columns are called, and the
14
- * shape of the answer.
15
- *
16
- * Measured before writing any of it (2026-08-15):
17
- *
18
- * - attaching Postgres pushes the filter down — `Filters: lang='fr'` reaches it over
19
- * 100 000 rows, so a real database is queried where it is and never copied;
20
- * - at PAGE size DuckDB is ~100× slower than two indexed reads (7 ms of floor per
21
- * query), so this must never sit on the ordinary read path;
22
- * - SQL Server cannot be attached at all — no `sqlserver` extension exists.
23
- */
1
+ /** Read across an app's sources — one SQL query over what can be attached. */
24
2
  import { type DuckDBConnection } from '@duckdb/node-api';
25
3
  type ShapeClass = abstract new (...args: any[]) => any;
26
4
  /** The engines DuckDB can attach — and the reason the list is short. */
@@ -44,41 +22,20 @@ export interface ConnectOptions {
44
22
  };
45
23
  /** The other places, exactly as `fougere.config.ts` states them. */
46
24
  sources?: Record<string, SourceDeclaration>;
47
- /**
48
- * What this scope may read — and therefore what gets attached at all.
49
- *
50
- * Not a check performed after the fact: a source holding none of these is never
51
- * attached, so its tables do not exist in this connection. `facadeFor` excludes an
52
- * entity with no door on purpose ("it would publish the auth tables to anyone who
53
- * asks"), and a SQL door at app scope would hand them over — this is what stops it.
54
- */
25
+ /** What this scope may read — and therefore what gets attached at all. */
55
26
  reads: readonly ShapeClass[];
56
27
  /** Same resolver the storage uses, when an app renames tables. */
57
28
  tableName?: (name: string) => string;
58
29
  }
59
30
  /** A query's answer: the rows, projected onto the shape that was named. */
60
31
  export interface Reads {
61
- /**
62
- * Name the shape the answer takes, then write the query.
63
- *
64
- * The tag is only reachable THROUGH the shape, so "no query without a declared
65
- * output" is held by the types rather than asked for. It matters: a raw `select *`
66
- * would return the fields `boundary.out: 'closed'` promises never leave — the one
67
- * strong guarantee the framework makes, walked around. The projection is the fence.
68
- */
32
+ /** Name the shape the answer takes, then write the query. */
69
33
  read<E extends ShapeClass>(shape: E): (sql: TemplateStringsArray, ...refs: unknown[]) => Promise<InstanceType<E>[]>;
70
34
  /** The attached sources, by alias — what the scope actually opened. */
71
35
  attached: ReadonlyMap<string, string>;
72
36
  close(): Promise<void>;
73
37
  }
74
- /**
75
- * Open a read-only view of the app's sources.
76
- *
77
- * `READ_ONLY` on every attach, and it is the engine that enforces it rather than a rule
78
- * anyone here remembers: this door reads across an app's whole storage, so it is
79
- * strictly more reachable than `storage.client` — which at least keeps the scope of its
80
- * entity — and a write through it would meet no judge at all.
81
- */
38
+ /** Open a read-only view of the app's sources. */
82
39
  export declare function connectSources(options: ConnectOptions): Promise<Reads>;
83
40
  export type { DuckDBConnection };
84
41
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAKzE,KAAK,UAAU,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAEvD,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,EAAE,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAC1D,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5C;;;;;;;OAOG;IACH,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAED,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB;;;;;;;OAOG;IACH,IAAI,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpH,uEAAuE;IACvE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,CA0D5E;AAuDD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAKzE,KAAK,UAAU,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AAEvD,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,EAAE,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,CAAA;KAAE,CAAC;IAC1D,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACtC;AAED,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,6DAA6D;IAC7D,IAAI,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpH,uEAAuE;IACvE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA8BD,kDAAkD;AAClD,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,CA2D5E;AAiDD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -1,28 +1,6 @@
1
- /**
2
- * Read across an app's sources — one SQL query over what can be attached.
3
- *
4
- * The by-key path (`findByKeys` and its dual) ENRICHES a page: it answers "I hold
5
- * these rows, give me the related ones". It cannot SELECT one — filtering, sorting,
6
- * paginating or counting on the other side collapses into reading that side whole.
7
- * That is the hole this closes, and it is not a reporting nicety: "my loans, newest
8
- * book first" crosses.
9
- *
10
- * What it is NOT is a query builder. A cross-source builder would be a Calcite in
11
- * TypeScript, and it would promise a composability the sources have not got. The query
12
- * stays SQL; what Fougere contributes is the three derivations a hand-written one would
13
- * duplicate — where each entity lives, what its table and columns are called, and the
14
- * shape of the answer.
15
- *
16
- * Measured before writing any of it (2026-08-15):
17
- *
18
- * - attaching Postgres pushes the filter down — `Filters: lang='fr'` reaches it over
19
- * 100 000 rows, so a real database is queried where it is and never copied;
20
- * - at PAGE size DuckDB is ~100× slower than two indexed reads (7 ms of floor per
21
- * query), so this must never sit on the ordinary read path;
22
- * - SQL Server cannot be attached at all — no `sqlserver` extension exists.
23
- */
1
+ /** Read across an app's sources — one SQL query over what can be attached. */
24
2
  import { DuckDBInstance } from '@duckdb/node-api';
25
- import { lowerFirst, fieldsOf } from '@fougere/schema';
3
+ import { lowerFirst } from '@fougere/schema';
26
4
  import { toTable, toTableName, toSnakeCase, codecsOf } from '@fougere/adapter-sql';
27
5
  const EXTENSION = {
28
6
  sqlite: 'sqlite',
@@ -34,11 +12,7 @@ const TYPE_CLAUSE = {
34
12
  postgres: 'postgres',
35
13
  mysql: 'mysql',
36
14
  };
37
- /**
38
- * The default source's alias — the same word `fougere.config.ts` uses for it.
39
- *
40
- * Not `main`: DuckDB reserves it, and an attach under that name is refused outright.
41
- */
15
+ /** The default source's alias — the same word `fougere.config.ts` uses for it. */
42
16
  const DEFAULT_ALIAS = 'db';
43
17
  /** 'Book' and 'book' name the same rows — the spelling `sources:` accepts either way. */
44
18
  function targetOf(declaration, alias) {
@@ -49,14 +23,7 @@ function targetOf(declaration, alias) {
49
23
  }
50
24
  return target;
51
25
  }
52
- /**
53
- * Open a read-only view of the app's sources.
54
- *
55
- * `READ_ONLY` on every attach, and it is the engine that enforces it rather than a rule
56
- * anyone here remembers: this door reads across an app's whole storage, so it is
57
- * strictly more reachable than `storage.client` — which at least keeps the scope of its
58
- * entity — and a write through it would meet no judge at all.
59
- */
26
+ /** Open a read-only view of the app's sources. */
60
27
  export async function connectSources(options) {
61
28
  const resolve = options.tableName ?? toTableName;
62
29
  // Where each named entity lives; anything unnamed is in the default source.
@@ -97,8 +64,9 @@ export async function connectSources(options) {
97
64
  attached,
98
65
  close: async () => { db.closeSync(); },
99
66
  read(shape) {
100
- const fields = fieldsOf(shape);
101
- const codecs = codecsOf(toTable('x', shape).columns);
67
+ const schema = shape;
68
+ const fields = schema.getFields();
69
+ const codecs = codecsOf(toTable('x', schema).columns);
102
70
  const names = Object.keys(fields);
103
71
  return async (parts, ...refs) => {
104
72
  const sql = parts.reduce((out, part, i) => out + part + (i < refs.length ? qualify(refs[i], placed) : ''), '');
@@ -126,13 +94,7 @@ function qualify(ref, placed) {
126
94
  `query text you write, or filter the result.`);
127
95
  }
128
96
  const quote = (identifier) => `"${identifier.replace(/"/g, '""')}"`;
129
- /**
130
- * The column set, checked ONCE against the shape.
131
- *
132
- * Not the JSON Schema judge per row: an aggregate is not a client's input, and the
133
- * caller here is the query its own author wrote. What must not pass silently is the two
134
- * disagreeing — a renamed alias yielding a column of nulls under the right type.
135
- */
97
+ /** The column set, checked ONCE against the shape. */
136
98
  function refuseMismatch(shape, names, row, sql) {
137
99
  const missing = names.filter((name) => !(toSnakeCase(name) in row) && !(name in row));
138
100
  if (missing.length === 0)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/adapter-duckdb",
3
- "version": "0.6.0-alpha.0",
3
+ "version": "0.7.0-alpha.0",
4
4
  "description": "Read across an app's sources — one SQL query over what DuckDB can attach.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -29,8 +29,8 @@
29
29
  "src"
30
30
  ],
31
31
  "dependencies": {
32
- "@fougere/schema": "0.6.0-alpha.0",
33
- "@fougere/adapter-sql": "0.6.0-alpha.0"
32
+ "@fougere/adapter-sql": "0.7.0-alpha.0",
33
+ "@fougere/schema": "0.7.0-alpha.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@duckdb/node-api": ">=1.4.0-0"
package/src/index.ts CHANGED
@@ -1,28 +1,6 @@
1
- /**
2
- * Read across an app's sources — one SQL query over what can be attached.
3
- *
4
- * The by-key path (`findByKeys` and its dual) ENRICHES a page: it answers "I hold
5
- * these rows, give me the related ones". It cannot SELECT one — filtering, sorting,
6
- * paginating or counting on the other side collapses into reading that side whole.
7
- * That is the hole this closes, and it is not a reporting nicety: "my loans, newest
8
- * book first" crosses.
9
- *
10
- * What it is NOT is a query builder. A cross-source builder would be a Calcite in
11
- * TypeScript, and it would promise a composability the sources have not got. The query
12
- * stays SQL; what Fougere contributes is the three derivations a hand-written one would
13
- * duplicate — where each entity lives, what its table and columns are called, and the
14
- * shape of the answer.
15
- *
16
- * Measured before writing any of it (2026-08-15):
17
- *
18
- * - attaching Postgres pushes the filter down — `Filters: lang='fr'` reaches it over
19
- * 100 000 rows, so a real database is queried where it is and never copied;
20
- * - at PAGE size DuckDB is ~100× slower than two indexed reads (7 ms of floor per
21
- * query), so this must never sit on the ordinary read path;
22
- * - SQL Server cannot be attached at all — no `sqlserver` extension exists.
23
- */
1
+ /** Read across an app's sources — one SQL query over what can be attached. */
24
2
  import { DuckDBInstance, type DuckDBConnection } from '@duckdb/node-api';
25
- import { lowerFirst, fieldsOf, type SchemaOrCard } from '@fougere/schema';
3
+ import { lowerFirst, type SchemaView } from '@fougere/schema';
26
4
  import { toTable, toTableName, toSnakeCase, codecsOf } from '@fougere/adapter-sql';
27
5
 
28
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -47,14 +25,7 @@ export interface ConnectOptions {
47
25
  db: { path?: string; attach?: string; type?: Attachable };
48
26
  /** The other places, exactly as `fougere.config.ts` states them. */
49
27
  sources?: Record<string, SourceDeclaration>;
50
- /**
51
- * What this scope may read — and therefore what gets attached at all.
52
- *
53
- * Not a check performed after the fact: a source holding none of these is never
54
- * attached, so its tables do not exist in this connection. `facadeFor` excludes an
55
- * entity with no door on purpose ("it would publish the auth tables to anyone who
56
- * asks"), and a SQL door at app scope would hand them over — this is what stops it.
57
- */
28
+ /** What this scope may read — and therefore what gets attached at all. */
58
29
  reads: readonly ShapeClass[];
59
30
  /** Same resolver the storage uses, when an app renames tables. */
60
31
  tableName?: (name: string) => string;
@@ -62,14 +33,7 @@ export interface ConnectOptions {
62
33
 
63
34
  /** A query's answer: the rows, projected onto the shape that was named. */
64
35
  export interface Reads {
65
- /**
66
- * Name the shape the answer takes, then write the query.
67
- *
68
- * The tag is only reachable THROUGH the shape, so "no query without a declared
69
- * output" is held by the types rather than asked for. It matters: a raw `select *`
70
- * would return the fields `boundary.out: 'closed'` promises never leave — the one
71
- * strong guarantee the framework makes, walked around. The projection is the fence.
72
- */
36
+ /** Name the shape the answer takes, then write the query. */
73
37
  read<E extends ShapeClass>(shape: E): (sql: TemplateStringsArray, ...refs: unknown[]) => Promise<InstanceType<E>[]>;
74
38
  /** The attached sources, by alias — what the scope actually opened. */
75
39
  attached: ReadonlyMap<string, string>;
@@ -88,11 +52,7 @@ const TYPE_CLAUSE: Record<Attachable, string> = {
88
52
  mysql: 'mysql',
89
53
  };
90
54
 
91
- /**
92
- * The default source's alias — the same word `fougere.config.ts` uses for it.
93
- *
94
- * Not `main`: DuckDB reserves it, and an attach under that name is refused outright.
95
- */
55
+ /** The default source's alias — the same word `fougere.config.ts` uses for it. */
96
56
  const DEFAULT_ALIAS = 'db';
97
57
 
98
58
  /** 'Book' and 'book' name the same rows — the spelling `sources:` accepts either way. */
@@ -108,14 +68,7 @@ function targetOf(declaration: { path?: string; attach?: string }, alias: string
108
68
  return target;
109
69
  }
110
70
 
111
- /**
112
- * Open a read-only view of the app's sources.
113
- *
114
- * `READ_ONLY` on every attach, and it is the engine that enforces it rather than a rule
115
- * anyone here remembers: this door reads across an app's whole storage, so it is
116
- * strictly more reachable than `storage.client` — which at least keeps the scope of its
117
- * entity — and a write through it would meet no judge at all.
118
- */
71
+ /** Open a read-only view of the app's sources. */
119
72
  export async function connectSources(options: ConnectOptions): Promise<Reads> {
120
73
  const resolve = options.tableName ?? toTableName;
121
74
 
@@ -162,8 +115,9 @@ export async function connectSources(options: ConnectOptions): Promise<Reads> {
162
115
  attached,
163
116
  close: async () => { db.closeSync(); },
164
117
  read<E extends ShapeClass>(shape: E) {
165
- const fields = fieldsOf(shape as unknown as SchemaOrCard);
166
- const codecs = codecsOf(toTable('x', shape as unknown as SchemaOrCard).columns);
118
+ const schema = shape as unknown as SchemaView;
119
+ const fields = schema.getFields();
120
+ const codecs = codecsOf(toTable('x', schema).columns);
167
121
  const names = Object.keys(fields);
168
122
 
169
123
  return async (parts: TemplateStringsArray, ...refs: unknown[]) => {
@@ -197,13 +151,7 @@ function qualify(ref: unknown, placed: Map<ShapeClass, { alias: string; table: s
197
151
 
198
152
  const quote = (identifier: string): string => `"${identifier.replace(/"/g, '""')}"`;
199
153
 
200
- /**
201
- * The column set, checked ONCE against the shape.
202
- *
203
- * Not the JSON Schema judge per row: an aggregate is not a client's input, and the
204
- * caller here is the query its own author wrote. What must not pass silently is the two
205
- * disagreeing — a renamed alias yielding a column of nulls under the right type.
206
- */
154
+ /** The column set, checked ONCE against the shape. */
207
155
  function refuseMismatch(shape: ShapeClass, names: string[], row: Record<string, unknown>, sql: string): void {
208
156
  const missing = names.filter((name) => !(toSnakeCase(name) in row) && !(name in row));
209
157
  if (missing.length === 0) return;