better-supabase 0.4.0 → 0.5.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/README.md +2 -0
- package/dist/channel-FQW-CnGR.d.ts +70 -0
- package/dist/cli/bin.js +1 -1
- package/dist/cli/codemod-DqV7612m.js +388 -0
- package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
- package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
- package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
- package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
- package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
- package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
- package/dist/cli/{doctor-BOym3mbL.js → doctor-L7Z8mYTx.js} +531 -50
- package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
- package/dist/cli/gen-BJc9vdyu.js +2 -0
- package/dist/cli/{gen-Ccn_p90L.js → gen-C2osRcr5.js} +283 -87
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/cli/{init-ClnxRYBF.js → init-Saf5-R9T.js} +2 -2
- package/dist/cli/{introspect-DxqfkVIv.js → introspect--H7oGmcd.js} +2 -2
- package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
- package/dist/cli/read-sets-BfMQcKy8.js +289 -0
- package/dist/cli/{run-CP20LKk_.js → run-BKR2W_79.js} +16 -12
- package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
- package/dist/cli/sql-DzPbfngx.js +348 -0
- package/dist/cli/{version-5mW_8sY1.js → version-BDJuO0UR.js} +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +3 -10
- package/dist/clone-BfgmLU90.js +21 -0
- package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -4
- package/dist/config/index.js +4 -4
- package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
- package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
- package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
- package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
- package/dist/edge/index.d.ts +16 -5
- package/dist/edge/index.js +10 -7
- package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-DUxU4oHe.d.ts} +1 -1
- package/dist/env/index.d.ts +2 -2
- package/dist/env/index.js +34 -1
- package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
- package/dist/events/index.d.ts +3 -2
- package/dist/events/index.js +65 -12
- package/dist/events-aIO7vsIk.d.ts +230 -0
- package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
- package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
- package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
- package/dist/hono/index.d.ts +17 -6
- package/dist/hono/index.js +40 -5
- package/dist/impersonation-VfaGc4Q_.js +41 -0
- package/dist/{index-BBdvo-5d.d.ts → index-BGdgBPdf.d.ts} +49 -6
- package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
- package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
- package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
- package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
- package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
- package/dist/{index-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
- package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
- package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
- package/dist/index.d.ts +30 -13
- package/dist/index.js +10 -9
- package/dist/jobs/index.d.ts +147 -101
- package/dist/jobs/index.js +744 -237
- package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
- package/dist/kit-events-CTFDfSwb.js +60 -0
- package/dist/kit-transport-B82k1djG.d.ts +33 -0
- package/dist/kit-transport-DJlHQUBU.js +55 -0
- package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
- package/dist/list/index.d.ts +1 -1
- package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
- package/dist/mcp/index.d.ts +25 -8
- package/dist/mcp/index.js +23 -14
- package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-CusDgPaD.d.ts} +5 -1
- package/dist/next/index.d.ts +31 -10
- package/dist/next/index.js +136 -27
- package/dist/notifications/index.d.ts +162 -0
- package/dist/notifications/index.js +264 -0
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +2 -2
- package/dist/orgs/index.d.ts +132 -0
- package/dist/orgs/index.js +245 -0
- package/dist/orpc/index.d.ts +28 -8
- package/dist/orpc/index.js +16 -3
- package/dist/otel/index.d.ts +21 -2
- package/dist/otel/index.js +50 -9
- package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
- package/dist/permdock-sql-DgOfeixi.js +51 -0
- package/dist/plugin-DwARMuQp.js +59 -0
- package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
- package/dist/plugins/actor/index.d.ts +5 -2
- package/dist/plugins/actor/index.js +22 -8
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +15 -10
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +14 -5
- package/dist/plugins/tenant/index.d.ts +2 -2
- package/dist/plugins/tenant/index.js +38 -20
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +4 -3
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +26 -6
- package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
- package/dist/postgres/index.d.ts +5 -5
- package/dist/postgres/index.js +20 -11
- package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
- package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
- package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +3 -3
- package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
- package/dist/queue-BYUCjr59.d.ts +252 -0
- package/dist/react/index.d.ts +8 -7
- package/dist/react/index.js +4 -3
- package/dist/react/server.d.ts +14 -3
- package/dist/react/server.js +10 -2
- package/dist/react/session.d.ts +7 -1
- package/dist/react/session.js +9 -1
- package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
- package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
- package/dist/realtime/index.d.ts +3 -3
- package/dist/realtime/index.js +5 -4
- package/dist/{refresh-BwYkyNOQ.d.ts → refresh-BTrQ_ZTp.d.ts} +1 -1
- package/dist/{resolve-BAqQcwMW.d.ts → resolve-C15TJq77.d.ts} +11 -5
- package/dist/{resource-My_y-Syh.js → resource-CRxxwkQq.js} +22 -7
- package/dist/{resource-BJcaIfxi.d.ts → resource-D8iqWMr-.d.ts} +5 -5
- package/dist/respond-D2OQl62-.d.ts +371 -0
- package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
- package/dist/server/index.d.ts +14 -12
- package/dist/server/index.js +11 -17
- package/dist/{server-BA75B0mT.js → server-Csx2sRjg.js} +251 -34
- package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
- package/dist/signers-Cc6r98SW.d.ts +162 -0
- package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
- package/dist/sql/index.d.ts +247 -10
- package/dist/sql/index.js +8590 -779
- package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +1 -2
- package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
- package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
- package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
- package/dist/storage/index.d.ts +4 -4
- package/dist/storage/index.js +155 -26
- package/dist/support-DT-OhSxD.d.ts +88 -0
- package/dist/support-Dk6vdmEw.js +291 -0
- package/dist/support-view-Da5peKFg.d.ts +17 -0
- package/dist/support-view-g4YLBeqX.js +18 -0
- package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
- package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
- package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
- package/dist/testing/index.d.ts +100 -10
- package/dist/testing/index.js +336 -8
- package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
- package/dist/user-change-CHFEUro1.js +49 -0
- package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
- package/dist/{version-Bmf7_FzM.js → version-DM_Roffb.js} +1 -1
- package/dist/{view-B64o81wp.d.ts → view-BEf0diC5.d.ts} +8 -3
- package/dist/{mfa-byfjruR9.js → view-Bq64ki8-.js} +95 -38
- package/dist/webhooks/index.d.ts +135 -4
- package/dist/webhooks/index.js +588 -2
- package/dist/wire-D2dT86vi.js +35 -0
- package/package.json +16 -8
- package/schemas/config-v1.json +131 -1
- package/schemas/doctor-report-v1.json +10 -0
- package/skills/better-supabase/SKILL.md +9 -6
- package/skills/better-supabase/references/plugins.md +6 -3
- package/skills/better-supabase/references/troubleshooting.md +1 -0
- package/skills/better-supabase-api/SKILL.md +9 -2
- package/skills/better-supabase-api/references/adapters.md +11 -8
- package/skills/better-supabase-auth/SKILL.md +3 -0
- package/skills/better-supabase-auth/references/permdock.md +56 -11
- package/dist/cli/gen-D5cLCULx.js +0 -2
- package/dist/cli/sql-D4VbqmXi.js +0 -199
- package/dist/events-B21UOhvP.d.ts +0 -84
- package/dist/executor-B5IoUxLE.d.ts +0 -195
- package/dist/invalidate-CgxmRamI.js +0 -24
- package/dist/permdock-sql-DImJULY-.d.ts +0 -17
- package/dist/plugin-BqR6wKMB.js +0 -23
- package/dist/respond-DgIbdrt8.d.ts +0 -194
- package/dist/view-BrJI2LKp.js +0 -45
package/schemas/config-v1.json
CHANGED
|
@@ -292,7 +292,10 @@
|
|
|
292
292
|
"type": "object",
|
|
293
293
|
"additionalProperties": false,
|
|
294
294
|
"properties": {
|
|
295
|
-
"scope": {
|
|
295
|
+
"scope": {
|
|
296
|
+
"type": "string",
|
|
297
|
+
"description": "The PermDock scope tenants are. Defaults to the manifest's root scope, the rls.scopes entry without within."
|
|
298
|
+
}
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
]
|
|
@@ -346,6 +349,11 @@
|
|
|
346
349
|
"description": "Tables that broadcast change signals (`table` or `schema.table`). Install with `better-supabase sql add realtime-tables`.",
|
|
347
350
|
"type": "array",
|
|
348
351
|
"items": { "type": "string" }
|
|
352
|
+
},
|
|
353
|
+
"global": {
|
|
354
|
+
"description": "Tables in `tables` without a tenant column, which broadcast on one topic for every signed-in user.",
|
|
355
|
+
"type": "array",
|
|
356
|
+
"items": { "type": "string" }
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
359
|
},
|
|
@@ -359,6 +367,14 @@
|
|
|
359
367
|
"kit": { "type": "array", "items": { "type": "string" } }
|
|
360
368
|
}
|
|
361
369
|
},
|
|
370
|
+
"kits": {
|
|
371
|
+
"type": "object",
|
|
372
|
+
"description": "How each SQL kit module maps onto the database, keyed by module name.",
|
|
373
|
+
"properties": {
|
|
374
|
+
"access": { "$ref": "#/$defs/accessKit" }
|
|
375
|
+
},
|
|
376
|
+
"additionalProperties": { "$ref": "#/$defs/kitModule" }
|
|
377
|
+
},
|
|
362
378
|
"seed": {
|
|
363
379
|
"type": "object",
|
|
364
380
|
"additionalProperties": false,
|
|
@@ -394,6 +410,120 @@
|
|
|
394
410
|
}
|
|
395
411
|
},
|
|
396
412
|
"$defs": {
|
|
413
|
+
"stringRecord": {
|
|
414
|
+
"type": "object",
|
|
415
|
+
"additionalProperties": { "type": "string" }
|
|
416
|
+
},
|
|
417
|
+
"kitModuleProperties": {
|
|
418
|
+
"mode": { "enum": ["managed", "adopt", "custom"], "default": "managed" },
|
|
419
|
+
"schema": { "type": "string", "default": "better_supabase" },
|
|
420
|
+
"tables": {
|
|
421
|
+
"type": "object",
|
|
422
|
+
"additionalProperties": { "type": ["string", "null"] }
|
|
423
|
+
},
|
|
424
|
+
"columns": {
|
|
425
|
+
"type": "object",
|
|
426
|
+
"additionalProperties": {
|
|
427
|
+
"type": "object",
|
|
428
|
+
"additionalProperties": { "type": ["string", "null"] }
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
"idType": { "type": "string" },
|
|
432
|
+
"triggerPrefix": { "type": "string", "default": "bs_" },
|
|
433
|
+
"permissions": { "$ref": "#/$defs/stringRecord" },
|
|
434
|
+
"options": { "type": "object" }
|
|
435
|
+
},
|
|
436
|
+
"kitModule": {
|
|
437
|
+
"type": "object",
|
|
438
|
+
"additionalProperties": false,
|
|
439
|
+
"properties": {
|
|
440
|
+
"mode": { "$ref": "#/$defs/kitModuleProperties/mode" },
|
|
441
|
+
"schema": { "$ref": "#/$defs/kitModuleProperties/schema" },
|
|
442
|
+
"tables": { "$ref": "#/$defs/kitModuleProperties/tables" },
|
|
443
|
+
"columns": { "$ref": "#/$defs/kitModuleProperties/columns" },
|
|
444
|
+
"idType": { "$ref": "#/$defs/kitModuleProperties/idType" },
|
|
445
|
+
"triggerPrefix": {
|
|
446
|
+
"$ref": "#/$defs/kitModuleProperties/triggerPrefix"
|
|
447
|
+
},
|
|
448
|
+
"permissions": { "$ref": "#/$defs/kitModuleProperties/permissions" },
|
|
449
|
+
"options": { "$ref": "#/$defs/kitModuleProperties/options" }
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
"accessKit": {
|
|
453
|
+
"type": "object",
|
|
454
|
+
"additionalProperties": false,
|
|
455
|
+
"properties": {
|
|
456
|
+
"mode": { "$ref": "#/$defs/kitModuleProperties/mode" },
|
|
457
|
+
"schema": { "$ref": "#/$defs/kitModuleProperties/schema" },
|
|
458
|
+
"tables": { "$ref": "#/$defs/kitModuleProperties/tables" },
|
|
459
|
+
"columns": { "$ref": "#/$defs/kitModuleProperties/columns" },
|
|
460
|
+
"idType": { "$ref": "#/$defs/kitModuleProperties/idType" },
|
|
461
|
+
"triggerPrefix": {
|
|
462
|
+
"$ref": "#/$defs/kitModuleProperties/triggerPrefix"
|
|
463
|
+
},
|
|
464
|
+
"permissions": { "$ref": "#/$defs/kitModuleProperties/permissions" },
|
|
465
|
+
"options": { "$ref": "#/$defs/kitModuleProperties/options" },
|
|
466
|
+
"model": {
|
|
467
|
+
"enum": ["roles", "catalog", "permdock", "custom"],
|
|
468
|
+
"default": "roles"
|
|
469
|
+
},
|
|
470
|
+
"roles": {
|
|
471
|
+
"type": "object",
|
|
472
|
+
"additionalProperties": {
|
|
473
|
+
"type": "array",
|
|
474
|
+
"items": { "type": "string" }
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"functions": {
|
|
478
|
+
"type": "object",
|
|
479
|
+
"additionalProperties": false,
|
|
480
|
+
"properties": {
|
|
481
|
+
"can": { "type": "string" },
|
|
482
|
+
"tenantIdsWith": { "type": "string" },
|
|
483
|
+
"isPlatform": { "type": "string" },
|
|
484
|
+
"canUser": { "type": "string" },
|
|
485
|
+
"canAssign": { "type": "string" },
|
|
486
|
+
"permissionClaims": { "type": "string" }
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"platformClaim": {
|
|
490
|
+
"type": "string",
|
|
491
|
+
"default": "platform_permissions"
|
|
492
|
+
},
|
|
493
|
+
"disabled": {
|
|
494
|
+
"type": "object",
|
|
495
|
+
"additionalProperties": false,
|
|
496
|
+
"properties": {
|
|
497
|
+
"tenant": { "type": "string" },
|
|
498
|
+
"tenantKey": { "type": "string" },
|
|
499
|
+
"user": { "type": "string" },
|
|
500
|
+
"userKey": { "type": "string" }
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
"activeTenant": {
|
|
504
|
+
"oneOf": [
|
|
505
|
+
{ "enum": ["claim", "resolver"] },
|
|
506
|
+
{
|
|
507
|
+
"type": "object",
|
|
508
|
+
"additionalProperties": false,
|
|
509
|
+
"required": ["profileColumn"],
|
|
510
|
+
"properties": {
|
|
511
|
+
"profileColumn": { "type": "string" },
|
|
512
|
+
"key": { "type": "string" }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
"permdock": {
|
|
518
|
+
"type": "object",
|
|
519
|
+
"additionalProperties": false,
|
|
520
|
+
"properties": {
|
|
521
|
+
"schema": { "type": "string" },
|
|
522
|
+
"scope": { "type": "string" }
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
},
|
|
397
527
|
"casing": { "enum": ["snake", "camel"] },
|
|
398
528
|
"privileges": {
|
|
399
529
|
"type": "array",
|
|
@@ -73,6 +73,14 @@
|
|
|
73
73
|
"BS304",
|
|
74
74
|
"BS305",
|
|
75
75
|
"BS306",
|
|
76
|
+
"BS307",
|
|
77
|
+
"BS308",
|
|
78
|
+
"BS309",
|
|
79
|
+
"BS310",
|
|
80
|
+
"BS311",
|
|
81
|
+
"BS312",
|
|
82
|
+
"BS313",
|
|
83
|
+
"BS314",
|
|
76
84
|
"BS401",
|
|
77
85
|
"BS402",
|
|
78
86
|
"BS403",
|
|
@@ -81,6 +89,8 @@
|
|
|
81
89
|
"BS406",
|
|
82
90
|
"BS407",
|
|
83
91
|
"BS408",
|
|
92
|
+
"BS409",
|
|
93
|
+
"BS410",
|
|
84
94
|
"BS501",
|
|
85
95
|
"BS502"
|
|
86
96
|
]
|
|
@@ -53,16 +53,19 @@ stay hidden.
|
|
|
53
53
|
|
|
54
54
|
## Workflow: after upgrading better-supabase
|
|
55
55
|
|
|
56
|
-
1.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
2. Run `pnpm better-supabase
|
|
56
|
+
1. Run `pnpm better-supabase codemod <version> --dry-run` for each minor
|
|
57
|
+
version you crossed (`codemod` without a name lists them), then without
|
|
58
|
+
`--dry-run`, and fix the lines it lists for review.
|
|
59
|
+
2. Run `pnpm better-supabase sql upgrade`, so the kit modules get the
|
|
60
|
+
release's SQL and any forward steps land in a migration, then write a
|
|
61
|
+
migration from the changed files.
|
|
62
|
+
3. Run `pnpm better-supabase gen` and commit the result. Since 0.3, a
|
|
60
63
|
composite foreign key such as `(customer_id, organization_id)` is named
|
|
61
64
|
after the remaining column (`customer`); set `tables.<name>.relations` in
|
|
62
65
|
the config to keep an old relation name.
|
|
63
|
-
|
|
66
|
+
4. Run `pnpm better-supabase skills install --check` and reinstall the skills
|
|
64
67
|
when it reports them stale.
|
|
65
|
-
|
|
68
|
+
5. Read the release's breaking changes in
|
|
66
69
|
https://github.com/ScaleDockHQ/better-supabase/blob/main/CHANGELOG.md
|
|
67
70
|
(guides live under https://bettersupabase.com/docs/migration). For 0.3: time values are
|
|
68
71
|
`Temporal` (see below), `--db-url` is gone, `doctor --format json` is
|
|
@@ -53,9 +53,12 @@ filled it.
|
|
|
53
53
|
RLS still decides what a user can read and write. `tenant()` makes queries
|
|
54
54
|
explicit and fills the column; it doesn't replace the policy.
|
|
55
55
|
|
|
56
|
-
The tenant comes from the
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
The tenant comes from `context.tenant`, which the server's `tenant` resolver
|
|
57
|
+
sets per request (the default `kits.access.activeTenant: 'resolver'`), then
|
|
58
|
+
the verified `tenant_id` claim (renamed with `claims.tenant` in
|
|
59
|
+
`better-supabase.config.ts`), then `app_metadata.tenant_id`. A resolver may
|
|
60
|
+
read the URL, but `current_tenant_id()` only returns a tenant the caller is a
|
|
61
|
+
member of. Never trust a tenant from `user_metadata` or a request body. When the project
|
|
59
62
|
has a `permdock.config.ts`, PermDock's hook writes that claim and the
|
|
60
63
|
memberships: don't run `sql add tenant`, and follow the PermDock reference of
|
|
61
64
|
the `better-supabase-auth` skill
|
|
@@ -16,6 +16,7 @@ Branch on `result.error.kind`, not on the message.
|
|
|
16
16
|
| `foreign_key` | Referenced row missing, or still referenced on delete | Create the parent first, or delete children |
|
|
17
17
|
| `not_null`, `check`, `exclusion` | A constraint rejected the row | Validate input earlier with the `validation()` plugin |
|
|
18
18
|
| `invalid_input` | Postgres couldn't parse a value (bad uuid, enum) | Validate before the call |
|
|
19
|
+
| `invalid_value` | A stored value the app type can't hold (`infinity` in a Temporal timestamp) | Store a real timestamp or `null`, or read the column without the codec |
|
|
19
20
|
| `validation` | A Standard Schema rejected the input; see `issues` | Show the issues to the user |
|
|
20
21
|
| `invalid_request` | The query can't run over PostgREST (for example a relation filter on `update`) | Read the keys first, or use a database function or `better-supabase/postgres` |
|
|
21
22
|
| `multiple_rows` | `findUnique` or a single-row write matched more than one row | Filter by a unique key |
|
|
@@ -88,11 +88,18 @@ for lists that clients read page by page: the list takes `after` instead of
|
|
|
88
88
|
|
|
89
89
|
## Background work (`better-supabase/jobs`, needs `sql add jobs idempotency webhook-inbox`)
|
|
90
90
|
|
|
91
|
-
- `createJobs(postgres.admin, { queue_name: zodSchema })` runs on Supabase Queues (pgmq): `enqueue` in the request, then `work` or `drain` in a worker. The handler throws to retry. `schedule(name, cron, queue, payload)` uses pg_cron. Queue names are lowercase letters, digits and underscores. With a service-role Supabase client instead of SQL, it uses the `pgmq_public` RPCs (no dedupe or schedules).
|
|
91
|
+
- `createJobs(postgres.admin, { queue_name: zodSchema })` runs on Supabase Queues (pgmq): `enqueue` in the request, then `work` or `drain` in a worker. The handler throws to retry. `schedule(name, cron, queue, payload, { timeZone })` uses pg_cron, or with `kits.jobs.options.scheduler: "drain"` a `jobs.drainRoute({ secret: process.env.CRON_SECRET, handlers })` route that Vercel Cron calls; `kits.jobs.options.backend: "table"` runs without pgmq. Queue names are lowercase letters, digits and underscores. With a service-role Supabase client instead of SQL, it uses the `pgmq_public` RPCs (no dedupe or schedules).
|
|
92
92
|
- `createIdempotency(postgres.admin).handle(request, handler)` for POST endpoints that clients retry.
|
|
93
93
|
- `createInbox(postgres.admin, { source, secrets }).receive(request)` for webhooks; `process(handler)` later.
|
|
94
94
|
- Times are `Temporal.Instant`: `EnqueueOptions.runAt`, `Job.enqueuedAt`, `Job.visibleUntil`, `InboxMessage.receivedAt` and webhook timestamps.
|
|
95
|
-
- Schedule cleanup with pg_cron at a quiet hour: `better_supabase.purge_job_archive('<queue>')`, `purge_webhooks()`, `purge_audit_log()` and `
|
|
95
|
+
- Schedule cleanup with pg_cron at a quiet hour: `better_supabase.purge_job_archive('<queue>')`, `purge_webhooks()`, `purge_audit_log()`, `purge_idempotency_keys()`, `purge_rate_limits()`, `purge_outbox()`, `purge_notifications()` and `purge_webhook_deliveries()`, for the modules you installed. Only `service_role` can execute them.
|
|
96
|
+
- Audit a table with `better_supabase.audit('public.t', redact => '{secret}', event_prefix => 't')`; record non-row events with `better_supabase.audit_event(event_type, ...)` and an `idempotency_key`. For per-tenant retention, write an `audit_retention(tenant)` SQL function or call `purgeAuditLog(sql, { retention })` from `better-supabase/jobs`.
|
|
97
|
+
- For a "view as user" support mode, add the `support-sessions` SQL module, pass `support: supportSessions({ store: sqlSupportStore(postgres) })` to `createServer`, call `bs.startSupport({ targetUserId, reason })` and `bs.stopSupport()` from server actions, and show a banner with `useSupportSession()`. Sessions are read-only by default; grant admins the `support.start` platform permission.
|
|
98
|
+
- For teams, add the `organizations` and `invitations` SQL modules and call them through `createOrgs({ transport: sqlTransport(postgres.asUser(claims)), events: betterSupabase.events, onInvite })` from `better-supabase/orgs`. Send the invitation email in `onInvite`, show `previewInvitation(token)` on the accept page, and switch with `orgs.switch(id)`, refreshing the session when it returns `refresh: true`. Errors carry codes such as `ORG_SLUG_TAKEN` in `hint`. Existing tables work with `mode: 'adopt'`.
|
|
99
|
+
- For user profiles, add the `profiles` SQL module: sign-up creates the row from auth metadata with a unique username and mirrors the email; users update only the `updatable` columns. Adopt an existing table with `mode: 'adopt'` and `syncTrigger: false` when your own trigger creates profiles. Use `avatarBucket()` and `orgLogoBucket()` from `better-supabase/storage` for uploads; `policy: { access: { read, write } }` checks any bucket through the access contract.
|
|
100
|
+
- For events other systems must see, add the `outbox` SQL module and write events with `emit_event(type, payload, subject, tenant, key)` inside the same transaction as the change (the kit modules emit theirs automatically). Relay them with `createOutbox(postgres.admin, { source }).relayRoute({ secret, consumers: { name: httpSink(url) } })` from `better-supabase/jobs` after `outbox.register(name, { types })`. Delivery is at least once; receivers deduplicate on the CloudEvent `id`.
|
|
101
|
+
- For in-app notifications, add the `notifications` SQL module and send with `createNotifications({ transport: sqlTransport(postgres.asUser(claims)), types })` from `better-supabase/notifications`, where `types` maps each notification type to a Standard Schema for its data. Pass a `key` so retries don't notify twice. Show them with `useNotifications({ topic, load })` from `better-supabase/react`; the topic defaults to `notifications:{userId}`. For email or push, pass `channels` and call `notifications.deliver()` from a cron route with a service connection.
|
|
102
|
+
- For webhooks to customers' endpoints, add the `webhooks-out` SQL module and publish with `createWebhooks({ transport: sqlTransport(postgres.admin) }).publish({ type, data, tenant, id })` from `better-supabase/webhooks`. Pass an `id` so retries don't send twice. Send with `webhooks.deliverRoute({ secret: process.env.CRON_SECRET })` from a cron route; deliveries are signed with Standard Webhooks and only go to public HTTPS URLs by default.
|
|
96
103
|
|
|
97
104
|
Done when a failing job is retried and then archived after `maxAttempts`,
|
|
98
105
|
a replayed webhook or POST doesn't run twice, and each kit table has a purge
|
|
@@ -15,6 +15,7 @@ export const betterSupabase = defineSupabase(schema);
|
|
|
15
15
|
## Next.js
|
|
16
16
|
|
|
17
17
|
```ts title="src/lib/supabase/server.ts"
|
|
18
|
+
import "server-only";
|
|
18
19
|
import { createNext } from "better-supabase/next";
|
|
19
20
|
|
|
20
21
|
import { betterSupabase } from "./index";
|
|
@@ -44,15 +45,15 @@ export const GET = bs.route(
|
|
|
44
45
|
## Hono
|
|
45
46
|
|
|
46
47
|
```ts title="src/server.ts"
|
|
47
|
-
import {
|
|
48
|
-
import { Hono } from "hono";
|
|
48
|
+
import { createHono } from "better-supabase/hono";
|
|
49
49
|
|
|
50
|
-
import {
|
|
50
|
+
import { betterSupabase } from "./lib/supabase";
|
|
51
51
|
|
|
52
52
|
const bs = createHono(betterSupabase);
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// bs.app() is new Hono<typeof bs.Env>() with bs.onError installed.
|
|
55
|
+
const app = bs
|
|
56
|
+
.app()
|
|
56
57
|
.use("/api/*", bs.middleware())
|
|
57
58
|
.get("/api/me", (c) => c.json({ kind: c.var.auth.kind }))
|
|
58
59
|
.route(
|
|
@@ -106,7 +107,7 @@ import { betterSupabase } from "../_shared/supabase.ts";
|
|
|
106
107
|
const bs = createMcp(betterSupabase, {
|
|
107
108
|
name: "crm",
|
|
108
109
|
version: "0.1.0",
|
|
109
|
-
|
|
110
|
+
advertisedScopes: ["openid", "crm.read"],
|
|
110
111
|
allowedOrigins: ["https://claude.ai"],
|
|
111
112
|
allowedHosts: ["crm.example.com", "localhost"],
|
|
112
113
|
resourceDocumentation: "https://crm.example.com/docs/mcp",
|
|
@@ -126,6 +127,8 @@ const bs = createMcp(betterSupabase, {
|
|
|
126
127
|
Deno.serve(bs.fetch);
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
Tools run as the calling user, so RLS applies to every tool call.
|
|
130
|
-
|
|
130
|
+
Tools run as the calling user, so RLS applies to every tool call.
|
|
131
|
+
`advertisedScopes` only advertises scopes; `requiredScopes` refuses delegated
|
|
132
|
+
tokens that lack one, and `authorize` refuses a single call. The session comes
|
|
133
|
+
from the `Authorization` header only, never from a cookie. A
|
|
131
134
|
`delegation` that is unset means the user's own token, which no scope limits.
|
|
@@ -106,6 +106,9 @@ membership lasts until the next refresh. When that window matters:
|
|
|
106
106
|
- Write policies that read the source of truth, for example
|
|
107
107
|
`organization_id in (select better_supabase.member_org_ids())`, instead of
|
|
108
108
|
the claim.
|
|
109
|
+
- For signed-out, banned or deleted users, add the `sessions` SQL module and a
|
|
110
|
+
restrictive policy with `using ((select better_supabase.session_active()))`
|
|
111
|
+
on the tables that guard sensitive data.
|
|
109
112
|
- After a change the user made themselves (joining an organization), call
|
|
110
113
|
`supabase.auth.refreshSession()` in the browser.
|
|
111
114
|
- In Next.js, drop the user's cached views with
|
|
@@ -26,29 +26,74 @@ the other.
|
|
|
26
26
|
`hasEntitlement(session, ...)` works.
|
|
27
27
|
- Run `permdock supabase inspect --out` before `sql add entitlements`. With
|
|
28
28
|
`permdock.manifest.json` present, the module reads PermDock's
|
|
29
|
-
`member_<scope>_ids` helpers and doesn't add `tenant`.
|
|
30
|
-
|
|
31
|
-
scope
|
|
29
|
+
`member_<scope>_ids` helpers and doesn't add `tenant`. The scope is the
|
|
30
|
+
manifest's root scope (the `rls.scopes` entry without `within`); set
|
|
31
|
+
`entitlements.permdock: { scope }` in `better-supabase.config.ts` for
|
|
32
|
+
another one.
|
|
32
33
|
- Name the same tenant claim on both sides. A non-default `claims.tenant` in
|
|
33
34
|
`better-supabase.config.ts` must also be PermDock's `rls.tenantClaim` and
|
|
34
35
|
go to `subjectFromSupabase` or `subjectFromSupabaseSession` as `{ tenant }`.
|
|
35
36
|
- Validate claims with PermDock's schema: `betterSupabase.claims(supabaseClaims().extend(appClaims))`.
|
|
36
|
-
- The helpers live in PermDock's `rls.schema
|
|
37
|
-
|
|
37
|
+
- The helpers live in PermDock's `rls.schema` (`permdock` by default). Buckets
|
|
38
|
+
and topics in `permdock` mode call them in `permdock` unless their `schema`
|
|
39
|
+
option names another schema; it must equal `rls.schema`.
|
|
40
|
+
- Use the scope names the manifest declares (`organization`, not PermDock's
|
|
41
|
+
`tenant` alias): the helpers are named after them, and doctor reports a
|
|
42
|
+
key checked at another scope than its catalog entry.
|
|
43
|
+
- The entitlements module needs PermDock's hook to fill `claims.features`
|
|
44
|
+
from `better_supabase.feature_claims`
|
|
45
|
+
(`supabase.hook.claims: { features: 'better_supabase.feature_claims' }`).
|
|
38
46
|
- The `permdock` policy mode checks role and scope only. Use it only for
|
|
39
47
|
permissions whose `rowConditions` is `false` in `permissions.catalog.json`.
|
|
48
|
+
A missing flag or a key the catalog doesn't list is unknown and refused;
|
|
49
|
+
regenerate the catalog with a current `permdock catalog`.
|
|
40
50
|
Leave permissions with row conditions to the policies
|
|
41
51
|
`permdock rls generate` writes; doctor reports BS214 and `permdock doctor`
|
|
42
52
|
PD037 otherwise.
|
|
43
53
|
- `permdock` mode compares ids as text, so a path or topic segment must be
|
|
44
54
|
the id's lowercase form.
|
|
45
55
|
- In MCP servers, narrow `tool.meta` with PermDock's `isPermission`, answer
|
|
46
|
-
`visible` with `mayUse` and `authorize` with `can`.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
`visible` with `mayUse` and `authorize` with `can`. A tool without a
|
|
57
|
+
PermDock permission in `meta` is denied: `visible` returns `false` and
|
|
58
|
+
`authorize` returns `{ allowed: false }`. Use `decide` instead of `can` to
|
|
59
|
+
put the denial reason in the refusal. `can` decides without a row, so check
|
|
60
|
+
row-conditioned permissions inside `run` or rely on RLS.
|
|
61
|
+
- In oRPC and Hono, pass the adapter's auth state to PermDock:
|
|
62
|
+
`createPermDock(policy, { subject: ({ context }) => subjectFromSupabaseSession(context.auth) })`
|
|
63
|
+
from `permdock/orpc`, or `(c) => subjectFromSupabaseSession(c.get("auth"))`
|
|
64
|
+
from `permdock/hono`. Fail closed: export one builder that takes a
|
|
65
|
+
permission (`(permission) => authed.use(pd.protect(permission))`, or the
|
|
66
|
+
route helper `[bs.middleware(), pd.protect(permission)]` in Hono), and don't
|
|
67
|
+
install `bs.middleware()` app-wide, so a route without a permission has no
|
|
68
|
+
`db`.
|
|
69
|
+
- In Next.js, cache the permission snapshot in a `'use cache: private'`
|
|
70
|
+
loader. The user id and the snapshot only exist after `bs.cached()`
|
|
71
|
+
returns, so pass it the static tags only, then tag and time the entry from
|
|
72
|
+
inside the loader with Next.js's `cacheTag` and `cacheLife`:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { cacheLife, cacheTag } from "next/cache";
|
|
76
|
+
import { snapshotFor } from "permdock";
|
|
77
|
+
import { cacheLifeFor, snapshotTag } from "permdock/next";
|
|
78
|
+
import { subjectFromSupabaseSession } from "permdock/supabase";
|
|
79
|
+
|
|
80
|
+
export async function loadSnapshot(orgId: string) {
|
|
81
|
+
"use cache: private";
|
|
82
|
+
const { session } = await bs.cached({ tags: [`org:${orgId}`] });
|
|
83
|
+
const snapshot = snapshotFor(policy, subjectFromSupabaseSession(session), {
|
|
84
|
+
tenant: orgId,
|
|
85
|
+
});
|
|
86
|
+
if (session.kind === "user") cacheTag(snapshotTag(session.user.id));
|
|
87
|
+
cacheLife(cacheLifeFor(snapshot));
|
|
88
|
+
return snapshot;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`bs.cached()` already caps the stale time at the token's expiry, and Next.js
|
|
93
|
+
keeps the smaller `stale` of the two `cacheLife` calls. When a role or plan
|
|
94
|
+
changes, drop the user's entries and the snapshot with
|
|
95
|
+
`bs.invalidateSession(userId, { tags: [snapshotTag(userId)] })`.
|
|
96
|
+
|
|
52
97
|
- Doctor BS405 measures `memberships` plus `attrs` against PermDock's 1 KB
|
|
53
98
|
budget and the whole token against 2 KB; run `doctor --as <user id>`.
|
|
54
99
|
|
package/dist/cli/gen-D5cLCULx.js
DELETED
package/dist/cli/sql-D4VbqmXi.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { r as defineCliCommand } from "./command-CK14fP91.js";
|
|
2
|
-
import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
|
|
3
|
-
import { i as plain, r as painter } from "./style-C0NFdJRL.js";
|
|
4
|
-
import { d as readSupabaseToml, f as schemaPaths, l as migrationCommand } from "./config-D9R0o1LH.js";
|
|
5
|
-
import { c as readPermdock, l as fileDiff, o as permdockConfig, r as entitlementsMode, t as compiledReadSets } from "./read-sets-CMa29t9a.js";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
-
import { existsSync } from "node:fs";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { SQL_MODULES, kitLayout, renderKit, resolveModules, sameKitFile } from "better-supabase/sql";
|
|
10
|
-
//#region src/cli/commands/sql.ts
|
|
11
|
-
const SQL_ARGS = {
|
|
12
|
-
action: {
|
|
13
|
-
type: "positional",
|
|
14
|
-
required: false,
|
|
15
|
-
description: "list (modules and whether they are installed), add <module...>, sync or print <module>"
|
|
16
|
-
},
|
|
17
|
-
check: {
|
|
18
|
-
type: "boolean",
|
|
19
|
-
description: "With sync: fail when a file is stale"
|
|
20
|
-
},
|
|
21
|
-
"tests-dir": {
|
|
22
|
-
type: "string",
|
|
23
|
-
description: "Where the pgtap module goes. Defaults to sql.testsDir",
|
|
24
|
-
valueHint: "dir"
|
|
25
|
-
},
|
|
26
|
-
"dry-run": {
|
|
27
|
-
type: "boolean",
|
|
28
|
-
description: "Show what would be written"
|
|
29
|
-
},
|
|
30
|
-
force: {
|
|
31
|
-
type: "boolean",
|
|
32
|
-
description: "Write tenant even though a permdock.config.ts is present"
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const USAGE = "Run `better-supabase sql --help` for the actions.";
|
|
36
|
-
/** Modules that fill a claim PermDock's hook also writes (`memberships`). */
|
|
37
|
-
const PERMDOCK_OWNED = /* @__PURE__ */ new Set(["tenant"]);
|
|
38
|
-
/** PermDock's helpers for the `entitlements` module; throws for a scope the manifest lacks. */
|
|
39
|
-
async function permdockFor(config) {
|
|
40
|
-
const mode = entitlementsMode(config, await readPermdock(config.root, config.permdock));
|
|
41
|
-
switch (mode.kind) {
|
|
42
|
-
case "tenant": return;
|
|
43
|
-
case "permdock": return mode.permdock;
|
|
44
|
-
case "invalid": throw new TypeError(mode.problem);
|
|
45
|
-
default: return mode;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function layout(config, args) {
|
|
49
|
-
return kitLayout(config, args["tests-dir"], [], await permdockFor(config));
|
|
50
|
-
}
|
|
51
|
-
/** The layout, with `config.readSets` compiled when `names` includes `read-sets`. */
|
|
52
|
-
async function layoutFor(config, args, names) {
|
|
53
|
-
const permdock = await permdockFor(config);
|
|
54
|
-
const needsReadSets = resolveModules(names, { ...permdock ? { permdock } : {} }).some((module) => module.name === "read-sets");
|
|
55
|
-
return kitLayout(config, args["tests-dir"], needsReadSets ? await compiledReadSets(config) : [], permdock);
|
|
56
|
-
}
|
|
57
|
-
async function write(config, args, names, kit) {
|
|
58
|
-
const lines = [];
|
|
59
|
-
const dryRun = args["dry-run"] === true;
|
|
60
|
-
for (const file of renderKit(names, kit)) {
|
|
61
|
-
const path = resolve(config.root, file.path);
|
|
62
|
-
const shown = display(config.root, file.path);
|
|
63
|
-
if (dryRun) {
|
|
64
|
-
lines.push(`Would write ${shown} (${file.module})`);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const wrote = await writeIfChanged(path, file.contents);
|
|
68
|
-
lines.push(`${wrote ? "Wrote" : "Unchanged"} ${shown} (${file.module})`);
|
|
69
|
-
}
|
|
70
|
-
return lines;
|
|
71
|
-
}
|
|
72
|
-
/** A note when migra's `schema_paths` is set and misses kit files, which `supabase db diff` would then skip. */
|
|
73
|
-
async function unlistedKitFiles(config, names, kit) {
|
|
74
|
-
const order = await schemaPaths(config.root, await readSupabaseToml(config.root));
|
|
75
|
-
if (!order.configured) return [];
|
|
76
|
-
const unlisted = new Set(order.unlisted);
|
|
77
|
-
const listed = new Set(order.files.filter((path) => !unlisted.has(path)));
|
|
78
|
-
const dir = `${config.sql.dir.replace(/\/$/, "")}/`;
|
|
79
|
-
const missing = renderKit(names, kit).map((file) => file.path).filter((path) => path.startsWith(dir) && !listed.has(path));
|
|
80
|
-
if (missing.length === 0) return [];
|
|
81
|
-
return [
|
|
82
|
-
"",
|
|
83
|
-
"supabase/config.toml sets [db.migrations] schema_paths, and no entry matches these files, so `supabase db diff` skips them.",
|
|
84
|
-
"Add them before the schemas that call their functions:",
|
|
85
|
-
...missing.map((path) => ` "./${path.startsWith("supabase/") ? path.slice(9) : path}",`)
|
|
86
|
-
];
|
|
87
|
-
}
|
|
88
|
-
async function runSql(config, args, paint = plain) {
|
|
89
|
-
const [action, ...names] = args._;
|
|
90
|
-
switch (action) {
|
|
91
|
-
case "list": {
|
|
92
|
-
const kit = await layout(config, args);
|
|
93
|
-
const files = new Map(renderKit(Object.keys(SQL_MODULES), kit).map((file) => [file.module, file.path]));
|
|
94
|
-
return {
|
|
95
|
-
code: 0,
|
|
96
|
-
output: `${Object.values(SQL_MODULES).map((module) => {
|
|
97
|
-
const path = files.get(module.name);
|
|
98
|
-
const installed = existsSync(resolve(config.root, path));
|
|
99
|
-
const tracked = config.sql.kit.includes(module.name);
|
|
100
|
-
const mark = installed ? tracked ? "●" : "○" : " ";
|
|
101
|
-
const requires = kit.permdock && module.permdockRequires ? module.permdockRequires : module.requires;
|
|
102
|
-
const needs = requires.length > 0 ? ` (needs ${requires.join(", ")})` : "";
|
|
103
|
-
return `${mark} ${module.name.padEnd(15)} ${module.description}${needs}`;
|
|
104
|
-
}).join("\n")}\n\n● installed and in sql.kit ○ installed, not in sql.kit`
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
case "add": {
|
|
108
|
-
if (names.length === 0) return {
|
|
109
|
-
code: 2,
|
|
110
|
-
error: `Name at least one module.\n${USAGE}`
|
|
111
|
-
};
|
|
112
|
-
const unknown = names.filter((name) => !(name in SQL_MODULES));
|
|
113
|
-
if (unknown.length > 0) return {
|
|
114
|
-
code: 2,
|
|
115
|
-
error: `Unknown module ${unknown.join(", ")}. Available: ${Object.keys(SQL_MODULES).join(", ")}`
|
|
116
|
-
};
|
|
117
|
-
const permdock = permdockConfig(config.root);
|
|
118
|
-
const hookModules = names.filter((name) => PERMDOCK_OWNED.has(name));
|
|
119
|
-
if (permdock && hookModules.length > 0 && args.force !== true) return {
|
|
120
|
-
code: 1,
|
|
121
|
-
error: [
|
|
122
|
-
`${permdock} is present, so PermDock owns the access token hook and the memberships claim.`,
|
|
123
|
-
`${hookModules.join(" and ")} would add a second source for them. Use \`permdock supabase hook generate\` instead,`,
|
|
124
|
-
"or pass --force to write the modules anyway."
|
|
125
|
-
].join("\n")
|
|
126
|
-
};
|
|
127
|
-
const kit = await layoutFor(config, args, names);
|
|
128
|
-
const lines = await write(config, args, names, kit);
|
|
129
|
-
const pulledIn = resolveModules(names, kit).map((module) => module.name).filter((name) => PERMDOCK_OWNED.has(name) && !names.includes(name));
|
|
130
|
-
if (permdock && pulledIn.length > 0) lines.push("", `${pulledIn.join(" and ")} came along as a dependency: its memberships table backs has_org_role() and has_entitlement().`, `PermDock's hook still owns the memberships claim, so don't call better_supabase.membership_claims from a hook.`, "List better_supabase.memberships as a PermDock membership source if both should agree.");
|
|
131
|
-
const untracked = resolveModules(names, kit).map((module) => module.name).filter((name) => !config.sql.kit.includes(name));
|
|
132
|
-
if (untracked.length > 0) lines.push("", `Add them to your config so \`sql sync --check\` keeps them current:`, ` sql: { kit: [${[...config.sql.kit, ...untracked].map((name) => `'${name}'`).join(", ")}] }`);
|
|
133
|
-
if (args["dry-run"] !== true) lines.push(...await unlistedKitFiles(config, names, kit));
|
|
134
|
-
lines.push("", `Then create a migration: ${migrationCommand(await readSupabaseToml(config.root), "better_supabase_kit")}`);
|
|
135
|
-
return {
|
|
136
|
-
code: 0,
|
|
137
|
-
output: lines.join("\n")
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
case "sync": {
|
|
141
|
-
if (config.sql.kit.length === 0) return {
|
|
142
|
-
code: 0,
|
|
143
|
-
output: "sql.kit is empty; nothing to sync."
|
|
144
|
-
};
|
|
145
|
-
if (args.check !== true) return {
|
|
146
|
-
code: 0,
|
|
147
|
-
output: (await write(config, args, config.sql.kit, await layoutFor(config, args, config.sql.kit))).join("\n")
|
|
148
|
-
};
|
|
149
|
-
const stale = [];
|
|
150
|
-
const diffs = [];
|
|
151
|
-
for (const file of renderKit(config.sql.kit, await layoutFor(config, args, config.sql.kit))) {
|
|
152
|
-
const current = await readFile(resolve(config.root, file.path), "utf8").catch(() => void 0);
|
|
153
|
-
if (!sameKitFile(current, file.contents)) {
|
|
154
|
-
const shown = display(config.root, file.path);
|
|
155
|
-
stale.push(shown);
|
|
156
|
-
diffs.push(fileDiff(shown, current, file.contents, paint));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return stale.length === 0 ? {
|
|
160
|
-
code: 0,
|
|
161
|
-
output: "SQL kit files are up to date."
|
|
162
|
-
} : {
|
|
163
|
-
code: 1,
|
|
164
|
-
output: diffs.join("\n\n"),
|
|
165
|
-
error: `Out of date: ${stale.join(", ")}. Run \`better-supabase sql sync\`.`
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
case "print": {
|
|
169
|
-
const [name] = names;
|
|
170
|
-
const module = name ? SQL_MODULES[name] : void 0;
|
|
171
|
-
if (!module) return {
|
|
172
|
-
code: 2,
|
|
173
|
-
error: `Name one module: ${Object.keys(SQL_MODULES).join(", ")}`
|
|
174
|
-
};
|
|
175
|
-
return {
|
|
176
|
-
code: 0,
|
|
177
|
-
output: module.render ? module.render(config.claims, await layout(config, args)) : module.sql
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
case void 0: return {
|
|
181
|
-
code: 2,
|
|
182
|
-
error: `Name an action.\n${USAGE}`
|
|
183
|
-
};
|
|
184
|
-
default: return {
|
|
185
|
-
code: 2,
|
|
186
|
-
error: `Unknown sql action "${action}".\n${USAGE}`
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const sqlCommand = defineCliCommand({
|
|
191
|
-
meta: {
|
|
192
|
-
name: "sql",
|
|
193
|
-
description: "Lists, adds, syncs and prints SQL kit modules"
|
|
194
|
-
},
|
|
195
|
-
args: SQL_ARGS,
|
|
196
|
-
run: (args, { config, io }) => runSql(config, args, painter(io.color))
|
|
197
|
-
});
|
|
198
|
-
//#endregion
|
|
199
|
-
export { sqlCommand };
|