( name: string, columns: TColumns, uniqueKey: readonly string[], )
| 94 | /** An owner-scoped table — partitioned by `(tenant, owner, subject)`, guarded by |
| 95 | * the executor owner policy. `uniqueKey` must include those three columns. */ |
| 96 | const ownedExecutorTable = <const TColumns extends UserColumns>( |
| 97 | name: string, |
| 98 | columns: TColumns, |
| 99 | uniqueKey: readonly string[], |
| 100 | ) => { |
| 101 | const out = table(name, { |
| 102 | ...columns, |
| 103 | row_id: idColumn("row_id", "varchar(255)").defaultTo$("auto"), |
| 104 | tenant: keyColumn("tenant"), |
| 105 | owner: keyColumn("owner"), |
| 106 | subject: keyColumn("subject"), |
| 107 | }); |
| 108 | out.unique(`${name}_uidx`, [...uniqueKey]); |
| 109 | return out.policy<ExecutorOwnerPolicyContext>({ |
| 110 | name: executorOwnerPolicyName, |
| 111 | onRead: ({ builder, context }) => ownerVisibility(builder, context), |
| 112 | onCreate: ({ values, context }) => assertOwnerWritable(name, values, context), |
| 113 | onUpdate: ({ builder, set, create, context }) => { |
| 114 | assertOwnerPatch(name, set, context); |
| 115 | assertOwnerPatch(name, create, context); |
| 116 | return ownerVisibility(builder, context); |
| 117 | }, |
| 118 | // A delete carries no values to assert against, so the reach guard is the |
| 119 | // ONLY thing standing between a widened (platform-view) context and a |
| 120 | // tenant-wide delete — `ownerVisibility` would happily match every row. |
| 121 | onDelete: ({ builder, context }) => { |
| 122 | assertReachReadOnly(name, "delete", context); |
| 123 | return ownerVisibility(builder, context); |
| 124 | }, |
| 125 | }); |
| 126 | }; |
| 127 | |
| 128 | const defineTables = <const TTables extends Record<string, AnyTable>>(tables: TTables): TTables => |
| 129 | tables; |
no test coverage detected