@edge-base/server 0.2.0 → 0.2.2

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 (87) hide show
  1. package/admin-build/_app/immutable/assets/19.4Si2ZFC_.css +1 -0
  2. package/admin-build/_app/immutable/assets/{3.Dg81Pgmd.css → 3.BtHYobTg.css} +1 -1
  3. package/admin-build/_app/immutable/assets/SqlEditor.Bbp1RIk0.css +1 -0
  4. package/admin-build/_app/immutable/assets/TableSqlTab.yeNZfhgG.css +1 -0
  5. package/admin-build/_app/immutable/chunks/4vlsb8ej.js +1 -0
  6. package/admin-build/_app/immutable/chunks/{CfPHB4r5.js → 5PDcRlfX.js} +1 -1
  7. package/admin-build/_app/immutable/chunks/{BSfSfeDG.js → B8DT4fss.js} +1 -1
  8. package/admin-build/_app/immutable/chunks/{DP9kmlCd.js → BEYYl662.js} +1 -1
  9. package/admin-build/_app/immutable/chunks/{B-WlnirM.js → BKXmgPq4.js} +1 -1
  10. package/admin-build/_app/immutable/chunks/{cqSkc6KP.js → BWyDPAjM.js} +1 -1
  11. package/admin-build/_app/immutable/chunks/{mD4EETH_.js → BaCHY17I.js} +1 -1
  12. package/admin-build/_app/immutable/chunks/C-DsDCNG.js +128 -0
  13. package/admin-build/_app/immutable/chunks/{2nyN5wuZ.js → C85dMlzL.js} +1 -1
  14. package/admin-build/_app/immutable/chunks/{DgxOZ3uv.js → CPdXvRUb.js} +1 -1
  15. package/admin-build/_app/immutable/chunks/{DpuSetmN.js → CTngeX8H.js} +1 -1
  16. package/admin-build/_app/immutable/chunks/{BKLsgaNT.js → DzXaj-Ja.js} +1 -1
  17. package/admin-build/_app/immutable/chunks/{D43CH5ty.js → c5iKSdWY.js} +1 -1
  18. package/admin-build/_app/immutable/chunks/{B14gOIqE.js → g3ZZdY-r.js} +1 -1
  19. package/admin-build/_app/immutable/chunks/{CN6aakgF.js → kiJ6KthZ.js} +1 -1
  20. package/admin-build/_app/immutable/chunks/lSpxLU5p.js +2 -0
  21. package/admin-build/_app/immutable/chunks/{uboHVq-x.js → qiZXAKh-.js} +1 -1
  22. package/admin-build/_app/immutable/entry/{app.Dc071f6C.js → app.BZxfavhF.js} +2 -2
  23. package/admin-build/_app/immutable/entry/start.Mr9mmopc.js +1 -0
  24. package/admin-build/_app/immutable/nodes/0.DlsaydXO.js +1 -0
  25. package/admin-build/_app/immutable/nodes/{1.rMaczUKT.js → 1.D2NWN5eG.js} +1 -1
  26. package/admin-build/_app/immutable/nodes/{10.DIOlO4hv.js → 10.EMDaN3nw.js} +1 -1
  27. package/admin-build/_app/immutable/nodes/{11.WxD9E0Eq.js → 11.BasqQ_o9.js} +1 -1
  28. package/admin-build/_app/immutable/nodes/{12.CNcefK3l.js → 12.DO31Ljs7.js} +1 -1
  29. package/admin-build/_app/immutable/nodes/{13.aAWsqDdR.js → 13.DhyAy-GZ.js} +1 -1
  30. package/admin-build/_app/immutable/nodes/{14.C9hdr3EN.js → 14.CLecGWc4.js} +1 -1
  31. package/admin-build/_app/immutable/nodes/{15.43r5uVx5.js → 15.B9kp3W4e.js} +1 -1
  32. package/admin-build/_app/immutable/nodes/{16.D519948J.js → 16.Pu_8T3RI.js} +1 -1
  33. package/admin-build/_app/immutable/nodes/{17.ks4I4yoH.js → 17.DX4z43t6.js} +1 -1
  34. package/admin-build/_app/immutable/nodes/{18.ZuNm22dY.js → 18.BKsSaxrr.js} +1 -1
  35. package/admin-build/_app/immutable/nodes/19.DXNF1htN.js +2 -0
  36. package/admin-build/_app/immutable/nodes/{20.C9ASlwCn.js → 20.VRVb0wee.js} +1 -1
  37. package/admin-build/_app/immutable/nodes/21.Ck3_0D2f.js +1 -0
  38. package/admin-build/_app/immutable/nodes/{22.6k8cg0Pr.js → 22.DqZf4CtH.js} +1 -1
  39. package/admin-build/_app/immutable/nodes/{23.B9hcFTU-.js → 23.DtyxMiQG.js} +1 -1
  40. package/admin-build/_app/immutable/nodes/{24.OsQM9QtS.js → 24.CloWNmTd.js} +1 -1
  41. package/admin-build/_app/immutable/nodes/{25.ClwkdaPp.js → 25.CnZWMq7_.js} +1 -1
  42. package/admin-build/_app/immutable/nodes/26.DrV7XOmf.js +1 -0
  43. package/admin-build/_app/immutable/nodes/{27.J1QASB3b.js → 27.DV8L32OF.js} +1 -1
  44. package/admin-build/_app/immutable/nodes/{28.BKP1tVcZ.js → 28.Stil2D4u.js} +1 -1
  45. package/admin-build/_app/immutable/nodes/{29.mqIe62On.js → 29.Zsm1e5Dc.js} +1 -1
  46. package/admin-build/_app/immutable/nodes/3.CKoj2vNz.js +2 -0
  47. package/admin-build/_app/immutable/nodes/{30.BRk-4B3j.js → 30.Ni0k5bER.js} +1 -1
  48. package/admin-build/_app/immutable/nodes/{31.BBqGNVXN.js → 31.mnqj9EbV.js} +1 -1
  49. package/admin-build/_app/immutable/nodes/{4.Bi91lv2V.js → 4.B_-z9AzT.js} +1 -1
  50. package/admin-build/_app/immutable/nodes/{5.BumjsbNK.js → 5.yiZ72j4k.js} +1 -1
  51. package/admin-build/_app/immutable/nodes/{6.CMTP_7xN.js → 6.BqykybBG.js} +1 -1
  52. package/admin-build/_app/immutable/nodes/{7.4T4wo7Kg.js → 7.BDAHlhsF.js} +1 -1
  53. package/admin-build/_app/immutable/nodes/{8.MUZQPNsN.js → 8.D8Xvy0lH.js} +1 -1
  54. package/admin-build/_app/immutable/nodes/{9.3SV00WXe.js → 9.Dddmd7_F.js} +1 -1
  55. package/admin-build/_app/version.json +1 -1
  56. package/admin-build/index.html +7 -7
  57. package/package.json +3 -2
  58. package/src/__tests__/functions-context.test.ts +5 -5
  59. package/src/__tests__/meta-export-coverage.test.ts +1 -0
  60. package/src/__tests__/pagination.test.ts +12 -8
  61. package/src/__tests__/postgres-dialect.test.ts +2 -2
  62. package/src/__tests__/query.test.ts +7 -7
  63. package/src/durable-objects/database-do.ts +3 -3
  64. package/src/durable-objects/logs-do.ts +2 -2
  65. package/src/lib/auth-d1-service.ts +1 -1
  66. package/src/lib/auth-d1.ts +10 -0
  67. package/src/lib/d1-handler.ts +23 -4
  68. package/src/lib/functions.ts +204 -397
  69. package/src/lib/internal-transport.ts +316 -0
  70. package/src/lib/pagination.ts +3 -3
  71. package/src/lib/plugin-migrations.ts +2 -2
  72. package/src/lib/postgres-handler.ts +2 -2
  73. package/src/lib/query-engine.ts +2 -2
  74. package/src/middleware/rate-limit.ts +11 -11
  75. package/src/routes/admin.ts +9 -3
  76. package/src/routes/auth.ts +13 -12
  77. package/src/routes/storage.ts +6 -12
  78. package/src/types.ts +2 -0
  79. package/admin-build/_app/immutable/assets/TableSqlTab.BHquaMBM.css +0 -1
  80. package/admin-build/_app/immutable/chunks/CkdaVlhQ.js +0 -2
  81. package/admin-build/_app/immutable/chunks/D8Nrx_IG.js +0 -128
  82. package/admin-build/_app/immutable/entry/start.Bhlxoqtt.js +0 -1
  83. package/admin-build/_app/immutable/nodes/0.CCfcYVV2.js +0 -1
  84. package/admin-build/_app/immutable/nodes/19.D519948J.js +0 -1
  85. package/admin-build/_app/immutable/nodes/21.BhSD2EfX.js +0 -1
  86. package/admin-build/_app/immutable/nodes/26._-65WG0q.js +0 -1
  87. package/admin-build/_app/immutable/nodes/3.WkDZWDQC.js +0 -2
@@ -5,12 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>EdgeBase Admin</title>
7
7
  <link rel="icon" href="/admin/favicon.svg" type="image/svg+xml" />
8
- <link href="/admin/_app/immutable/entry/start.Bhlxoqtt.js" rel="modulepreload">
9
- <link href="/admin/_app/immutable/chunks/uboHVq-x.js" rel="modulepreload">
8
+ <link href="/admin/_app/immutable/entry/start.Mr9mmopc.js" rel="modulepreload">
9
+ <link href="/admin/_app/immutable/chunks/qiZXAKh-.js" rel="modulepreload">
10
10
  <link href="/admin/_app/immutable/chunks/BdTBlfLy.js" rel="modulepreload">
11
11
  <link href="/admin/_app/immutable/chunks/Bn2NtlTj.js" rel="modulepreload">
12
- <link href="/admin/_app/immutable/chunks/CfPHB4r5.js" rel="modulepreload">
13
- <link href="/admin/_app/immutable/entry/app.Dc071f6C.js" rel="modulepreload">
12
+ <link href="/admin/_app/immutable/chunks/5PDcRlfX.js" rel="modulepreload">
13
+ <link href="/admin/_app/immutable/entry/app.BZxfavhF.js" rel="modulepreload">
14
14
  <link href="/admin/_app/immutable/chunks/B2bEC_Hm.js" rel="modulepreload">
15
15
  <link href="/admin/_app/immutable/chunks/Bb0e0sAP.js" rel="modulepreload">
16
16
  <link href="/admin/_app/immutable/chunks/DtZk82gG.js" rel="modulepreload">
@@ -26,7 +26,7 @@
26
26
  <div style="display: contents">
27
27
  <script>
28
28
  {
29
- __sveltekit_18bu3q0 = {
29
+ __sveltekit_1ddhohc = {
30
30
  base: "/admin",
31
31
  assets: "/admin"
32
32
  };
@@ -34,8 +34,8 @@
34
34
  const element = document.currentScript.parentElement;
35
35
 
36
36
  Promise.all([
37
- import("/admin/_app/immutable/entry/start.Bhlxoqtt.js"),
38
- import("/admin/_app/immutable/entry/app.Dc071f6C.js")
37
+ import("/admin/_app/immutable/entry/start.Mr9mmopc.js"),
38
+ import("/admin/_app/immutable/entry/app.BZxfavhF.js")
39
39
  ]).then(([kit, app]) => {
40
40
  kit.start(app, element);
41
41
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edge-base/server",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "EdgeBase runtime assets consumed by the EdgeBase CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,7 +34,8 @@
34
34
  "jose": "^6.0.0",
35
35
  "pg": "^8.16.3",
36
36
  "zod": "^4.3.6",
37
- "@edge-base/shared": "0.2.0"
37
+ "@edge-base/shared": "0.2.2",
38
+ "@edge-base/core": "0.2.2"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@cloudflare/vitest-pool-workers": "^0.8.71",
@@ -33,7 +33,7 @@ describe('buildFunctionContext admin.db', () => {
33
33
  workerUrl: 'http://localhost:8787',
34
34
  });
35
35
 
36
- const result = await ctx.admin.db('shared').table('posts').list({ limit: 5 });
36
+ const result = await ctx.admin.db('shared').table('posts').limit(5).getList();
37
37
 
38
38
  expect(result.items).toHaveLength(1);
39
39
  expect(fetchMock).toHaveBeenCalledWith(
@@ -97,7 +97,7 @@ describe('buildFunctionContext admin.db', () => {
97
97
  );
98
98
  });
99
99
 
100
- it('normalizes admin.sql worker responses to row arrays', async () => {
100
+ it('normalizes admin.sqlWithDirectD1Access worker responses to row arrays', async () => {
101
101
  const fetchMock = vi.fn().mockResolvedValue(
102
102
  new Response(JSON.stringify({
103
103
  rows: [{ total: 2 }],
@@ -129,7 +129,7 @@ describe('buildFunctionContext admin.db', () => {
129
129
  serviceKey: 'sk-test',
130
130
  });
131
131
 
132
- const rows = await ctx.admin.sql('shared', undefined, 'SELECT COUNT(*) AS total FROM posts');
132
+ const rows = await ctx.admin.sqlWithDirectD1Access('shared', undefined, 'SELECT COUNT(*) AS total FROM posts');
133
133
 
134
134
  expect(rows).toEqual([{ total: 2 }]);
135
135
  expect(fetchMock).toHaveBeenCalledWith(
@@ -144,7 +144,7 @@ describe('buildFunctionContext admin.db', () => {
144
144
  );
145
145
  });
146
146
 
147
- it('routes admin.sql through the database DO when env is available', async () => {
147
+ it('routes admin.sqlWithDirectD1Access through the database DO when env is available', async () => {
148
148
  const fetchMock = vi.fn();
149
149
  vi.stubGlobal('fetch', fetchMock);
150
150
 
@@ -196,7 +196,7 @@ describe('buildFunctionContext admin.db', () => {
196
196
  serviceKey: 'sk-test',
197
197
  });
198
198
 
199
- const rows = await ctx.admin.sql('workspace', 'ws-1', 'SELECT COUNT(*) AS total FROM members', []);
199
+ const rows = await ctx.admin.sqlWithDirectD1Access('workspace', 'ws-1', 'SELECT COUNT(*) AS total FROM members', []);
200
200
 
201
201
  expect(rows).toEqual([{ total: 3 }]);
202
202
  expect(stub.fetch).toHaveBeenCalledTimes(2);
@@ -28,6 +28,7 @@ const UNTESTED_LIBS = new Set([
28
28
  'email-provider.ts',
29
29
  'email-translations.ts', // email i18n strings — used by auth email flows, tested via integration
30
30
  'functions.ts',
31
+ 'internal-transport.ts', // internal transport adapter — tested indirectly via functions-context tests
31
32
  'hono.ts',
32
33
  'log-writer.ts',
33
34
  'plugin-migrations.ts',
@@ -10,7 +10,7 @@ import { parsePagination } from '../lib/pagination.js';
10
10
  describe('parsePagination', () => {
11
11
  // ── Defaults ──
12
12
  it('returns defaults when no params provided', () => {
13
- expect(parsePagination(undefined, undefined)).toEqual({ limit: 20, offset: 0 });
13
+ expect(parsePagination(undefined, undefined)).toEqual({ limit: 100, offset: 0 });
14
14
  });
15
15
 
16
16
  // ── Valid values ──
@@ -23,21 +23,25 @@ describe('parsePagination', () => {
23
23
  });
24
24
 
25
25
  // ── Limit clamping ──
26
- it('clamps limit to 100', () => {
27
- expect(parsePagination('999', '0')).toEqual({ limit: 100, offset: 0 });
26
+ it('clamps limit to 1000', () => {
27
+ expect(parsePagination('9999', '0')).toEqual({ limit: 1000, offset: 0 });
28
+ });
29
+
30
+ it('accepts limit within range (500)', () => {
31
+ expect(parsePagination('500', '0')).toEqual({ limit: 500, offset: 0 });
28
32
  });
29
33
 
30
34
  it('rejects limit=0 (falls back to default)', () => {
31
- expect(parsePagination('0', '0')).toEqual({ limit: 20, offset: 0 });
35
+ expect(parsePagination('0', '0')).toEqual({ limit: 100, offset: 0 });
32
36
  });
33
37
 
34
38
  // ── REGRESSION: negative and NaN values ──
35
39
  it('rejects negative limit', () => {
36
- expect(parsePagination('-5', '0')).toEqual({ limit: 20, offset: 0 });
40
+ expect(parsePagination('-5', '0')).toEqual({ limit: 100, offset: 0 });
37
41
  });
38
42
 
39
43
  it('rejects NaN limit', () => {
40
- expect(parsePagination('abc', '0')).toEqual({ limit: 20, offset: 0 });
44
+ expect(parsePagination('abc', '0')).toEqual({ limit: 100, offset: 0 });
41
45
  });
42
46
 
43
47
  it('rejects negative offset', () => {
@@ -49,11 +53,11 @@ describe('parsePagination', () => {
49
53
  });
50
54
 
51
55
  it('rejects Infinity limit', () => {
52
- expect(parsePagination('Infinity', '0')).toEqual({ limit: 20, offset: 0 });
56
+ expect(parsePagination('Infinity', '0')).toEqual({ limit: 100, offset: 0 });
53
57
  });
54
58
 
55
59
  // ── Empty string (same as undefined) ──
56
60
  it('treats empty strings as defaults', () => {
57
- expect(parsePagination('', '')).toEqual({ limit: 20, offset: 0 });
61
+ expect(parsePagination('', '')).toEqual({ limit: 100, offset: 0 });
58
62
  });
59
63
  });
@@ -43,7 +43,7 @@ describe('PostgreSQL dialect — bind params', () => {
43
43
  const { sql, params } = buildListQuery('products', {}, 'postgres');
44
44
  expect(sql).toContain('LIMIT $1');
45
45
  expect(sql).not.toContain('?');
46
- expect(params).toEqual([20]);
46
+ expect(params).toEqual([100]);
47
47
  });
48
48
 
49
49
  it('buildListQuery with offset → $1 OFFSET $2', () => {
@@ -71,7 +71,7 @@ describe('PostgreSQL dialect — bind params', () => {
71
71
  expect(sql).toContain('"status" = $1');
72
72
  expect(sql).toContain('"price" > $2');
73
73
  expect(sql).toContain('LIMIT $3');
74
- expect(params).toEqual(['active', 100, 20]);
74
+ expect(params).toEqual(['active', 100, 100]);
75
75
  });
76
76
 
77
77
  it('buildListQuery cursor after → $1 for cursor, $2 for limit', () => {
@@ -79,10 +79,10 @@ describe('buildListQuery — no filters', () => {
79
79
  expect(sql).toContain('LIMIT');
80
80
  });
81
81
 
82
- it('default limit is 20', () => {
82
+ it('default limit is 100', () => {
83
83
  const { params } = buildListQuery('posts', {});
84
- // params should contain 20 as default limit
85
- expect(params).toContain(20);
84
+ // params should contain 100 as default limit
85
+ expect(params).toContain(100);
86
86
  });
87
87
 
88
88
  it('generates countSql for non-cursor pagination', () => {
@@ -313,9 +313,9 @@ describe('buildSearchQuery', () => {
313
313
  expect(sql).toContain('"posts_fts"');
314
314
  });
315
315
 
316
- it('default limit 20, offset 0', () => {
316
+ it('default limit 100, offset 0', () => {
317
317
  const { params } = buildSearchQuery('posts', 'q');
318
- expect(params[1]).toBe(20);
318
+ expect(params[1]).toBe(100);
319
319
  expect(params[2]).toBeUndefined();
320
320
  });
321
321
 
@@ -560,7 +560,7 @@ describe('3-way sync: QUERY_PARAM_KEYS ↔ Zod queryParamsSchema', () => {
560
560
  describe('buildListQuery — exact params verification', () => {
561
561
  it('no filters → params contains only default limit', () => {
562
562
  const { params } = buildListQuery('t', {});
563
- expect(params).toEqual([20]);
563
+ expect(params).toEqual([100]);
564
564
  });
565
565
 
566
566
  it('no filters → countParams is empty array', () => {
@@ -722,7 +722,7 @@ describe('buildSubstringSearchQuery', () => {
722
722
  const { sql, params } = buildSubstringSearchQuery('posts', '준규', { fields: ['title', 'content'] });
723
723
  expect(sql).toContain('instr(lower(CAST("title" AS TEXT)), lower(?)) > 0');
724
724
  expect(sql).toContain('instr(lower(CAST("content" AS TEXT)), lower(?)) > 0');
725
- expect(params).toEqual(['준규', '준규', 20]);
725
+ expect(params).toEqual(['준규', '준규', 100]);
726
726
  });
727
727
 
728
728
  it('passes the raw term through the SQLite instr() fallback', () => {
@@ -470,7 +470,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
470
470
  if (countSql && countParams) {
471
471
  const countResult = [...this.sql(countSql, ...countParams)];
472
472
  const total = (countResult[0]?.total as number) ?? 0;
473
- const perPage = options.pagination?.perPage ?? options.pagination?.limit ?? 20;
473
+ const perPage = options.pagination?.perPage ?? options.pagination?.limit ?? 100;
474
474
  response.total = total;
475
475
  response.page = options.pagination?.page ?? 1;
476
476
  response.perPage = perPage;
@@ -478,7 +478,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
478
478
 
479
479
  // Cursor pagination: always include cursor and hasMore when items exist
480
480
  // so clients can start cursor-based pagination from any page (including the first)
481
- const limit = options.pagination?.limit ?? options.pagination?.perPage ?? 20;
481
+ const limit = options.pagination?.limit ?? options.pagination?.perPage ?? 100;
482
482
  const hasMore = normalizedRows.length === limit;
483
483
  response.hasMore = hasMore;
484
484
  if (normalizedRows.length > 0) {
@@ -516,7 +516,7 @@ export class DatabaseDO extends DurableObject<DOEnv> {
516
516
  return c.json({ items: [] });
517
517
  }
518
518
 
519
- const limit = options.pagination?.limit ?? options.pagination?.perPage ?? 20;
519
+ const limit = options.pagination?.limit ?? options.pagination?.perPage ?? 100;
520
520
  const offset = options.pagination?.offset ?? ((options.pagination?.page ?? 1) - 1) * limit;
521
521
 
522
522
  const tableConfig = this.getTableConfig(name);
@@ -484,10 +484,10 @@ export class LogsDO extends DurableObject<LogsDOEnv> {
484
484
  whereParts.push('status >= ?');
485
485
  params.push(SERVER_ERROR_STATUS);
486
486
  } else if (level === 'warn') {
487
- whereParts.push('status >= ? AND status < ?');
487
+ whereParts.push('(status >= ? AND status < ? AND status != 304)');
488
488
  params.push(300, SERVER_ERROR_STATUS);
489
489
  } else if (level === 'info') {
490
- whereParts.push('status >= ? AND status < ?');
490
+ whereParts.push('(status >= ? AND status < ? OR status = 304)');
491
491
  params.push(200, 300);
492
492
  }
493
493
 
@@ -194,7 +194,7 @@ export async function updateUser(
194
194
  'email', 'passwordHash', 'displayName', 'avatarUrl', 'emailVisibility',
195
195
  'role', 'status', 'verified', 'isAnonymous', 'locale', 'metadata', 'appMetadata',
196
196
  'customClaims', 'phone', 'phoneVerified', 'disabled', 'bannedUntil',
197
- 'lastSignInAt', 'updatedAt',
197
+ 'lastSignInAt', 'lastSignedInAt', 'updatedAt',
198
198
  ]);
199
199
 
200
200
  const sets: string[] = [];
@@ -125,6 +125,7 @@ CREATE TABLE IF NOT EXISTS _users (
125
125
  disabled INTEGER DEFAULT 0,
126
126
  status TEXT DEFAULT 'active',
127
127
  locale TEXT DEFAULT 'en',
128
+ lastSignedInAt TEXT,
128
129
  createdAt TEXT NOT NULL,
129
130
  updatedAt TEXT NOT NULL
130
131
  );
@@ -309,6 +310,7 @@ CREATE TABLE IF NOT EXISTS _users (
309
310
  disabled INTEGER DEFAULT 0,
310
311
  status TEXT DEFAULT 'active',
311
312
  locale TEXT DEFAULT 'en',
313
+ lastSignedInAt TEXT,
312
314
  createdAt TEXT NOT NULL,
313
315
  updatedAt TEXT NOT NULL
314
316
  );
@@ -403,6 +405,14 @@ export async function ensureAuthSchema(db: AuthDb): Promise<void> {
403
405
  .filter((s) => s.length > 0);
404
406
 
405
407
  await db.batch(statements.map((sql) => ({ sql })));
408
+
409
+ // Migrate existing _users tables: add lastSignedInAt if missing
410
+ try {
411
+ await db.run('ALTER TABLE _users ADD COLUMN lastSignedInAt TEXT', []);
412
+ } catch {
413
+ // Column already exists — safe to ignore
414
+ }
415
+
406
416
  schemaInitialized = true;
407
417
  }
408
418
 
@@ -399,8 +399,27 @@ async function handleList(
399
399
  }
400
400
 
401
401
  const queryOpts = parseQueryParams(Object.fromEntries(new URL(c.req.url).searchParams));
402
- const { sql, params, countSql, countParams } = buildListQuery(tableName, queryOpts, 'sqlite');
403
- const result = await executeD1Query(resolved.db, sql, params);
402
+ let query = buildListQuery(tableName, queryOpts, 'sqlite');
403
+ let result;
404
+ try {
405
+ result = await executeD1Query(resolved.db, query.sql, query.params);
406
+ } catch {
407
+ // FTS table may not exist — fall back to substring search
408
+ if (queryOpts.search) {
409
+ const searchFields = tableConfig.schema ? Object.keys(tableConfig.schema).filter(k => tableConfig.schema![k] !== false) : ['id'];
410
+ query = buildSubstringSearchQuery(tableName, queryOpts.search, {
411
+ pagination: queryOpts.pagination,
412
+ filters: queryOpts.filters,
413
+ orFilters: queryOpts.orFilters,
414
+ sort: queryOpts.sort,
415
+ fields: searchFields,
416
+ }, 'sqlite');
417
+ result = await executeD1Query(resolved.db, query.sql, query.params);
418
+ } else {
419
+ throw new Error('Query failed');
420
+ }
421
+ }
422
+ const { countSql, countParams } = query;
404
423
 
405
424
  // Apply read rules per row + normalize booleans/JSON
406
425
  let items = result.rows.map(r => normalizeRow(stripInternalFields(r), tableConfig));
@@ -435,7 +454,7 @@ async function handleList(
435
454
  total = Number(countResult.rows[0]?.total ?? 0);
436
455
  }
437
456
 
438
- const perPage = queryOpts.pagination?.limit ?? queryOpts.pagination?.perPage ?? 20;
457
+ const perPage = queryOpts.pagination?.limit ?? queryOpts.pagination?.perPage ?? 100;
439
458
  const page = queryOpts.pagination?.page ?? 1;
440
459
  // Always include cursor/hasMore like DO does — clients can start cursor pagination from any page
441
460
  const hasMore = items.length === perPage;
@@ -497,7 +516,7 @@ async function handleSearch(
497
516
 
498
517
  let items: Record<string, unknown>[];
499
518
  let total = 0;
500
- const limit = queryOpts.pagination?.limit ?? queryOpts.pagination?.perPage ?? 20;
519
+ const limit = queryOpts.pagination?.limit ?? queryOpts.pagination?.perPage ?? 100;
501
520
  const offset = queryOpts.pagination?.offset ?? ((queryOpts.pagination?.page ?? 1) - 1) * limit;
502
521
  const searchQuery = buildSearchQuery(tableName, searchTerm, {
503
522
  pagination: queryOpts.pagination,