@supacloud/cli 0.99.0 → 0.101.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.js
CHANGED
|
@@ -3177,23 +3177,22 @@ function InstanceKeys(value) {
|
|
|
3177
3177
|
function IsIEEE754(value) {
|
|
3178
3178
|
return typeof value === "number";
|
|
3179
3179
|
}
|
|
3180
|
-
var ByteMarker
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
})(ByteMarker || (ByteMarker = {}));
|
|
3180
|
+
var ByteMarker = {
|
|
3181
|
+
Array: 0,
|
|
3182
|
+
BigInt: 1,
|
|
3183
|
+
Boolean: 2,
|
|
3184
|
+
Date: 3,
|
|
3185
|
+
Constructor: 4,
|
|
3186
|
+
Function: 5,
|
|
3187
|
+
Null: 6,
|
|
3188
|
+
Number: 7,
|
|
3189
|
+
Object: 8,
|
|
3190
|
+
RegExp: 9,
|
|
3191
|
+
String: 10,
|
|
3192
|
+
Symbol: 11,
|
|
3193
|
+
TypeArray: 12,
|
|
3194
|
+
Undefined: 13
|
|
3195
|
+
};
|
|
3197
3196
|
var Accumulator = BigInt("14695981039346656037");
|
|
3198
3197
|
var [Prime, Size] = [BigInt("1099511628211"), BigInt("18446744073709551616")];
|
|
3199
3198
|
var Bytes = Array.from({ length: 256 }).map((_, i) => BigInt(i));
|
|
@@ -15196,7 +15195,7 @@ function scaffoldFiles(framework, slug) {
|
|
|
15196
15195
|
"package.json": JSON.stringify({
|
|
15197
15196
|
private: true,
|
|
15198
15197
|
type: "module",
|
|
15199
|
-
dependencies: { elysia: "2.0.0-beta.
|
|
15198
|
+
dependencies: { elysia: "2.0.0-beta.32" }
|
|
15200
15199
|
}, null, 2) + `
|
|
15201
15200
|
`,
|
|
15202
15201
|
"index.ts": `import { Elysia } from "elysia";
|
|
@@ -21389,34 +21388,34 @@ var starter_metadata_default = {
|
|
|
21389
21388
|
generatedBy: "supacloud/starter-metadata-v1",
|
|
21390
21389
|
packages: {
|
|
21391
21390
|
compiler: {
|
|
21392
|
-
version: "0.46.
|
|
21391
|
+
version: "0.46.3"
|
|
21393
21392
|
},
|
|
21394
21393
|
app: {
|
|
21395
|
-
version: "0.27.
|
|
21394
|
+
version: "0.27.1",
|
|
21396
21395
|
dependencies: {
|
|
21397
21396
|
rxjs: "7.8.2"
|
|
21398
21397
|
}
|
|
21399
21398
|
},
|
|
21400
21399
|
elysia: {
|
|
21401
|
-
version: "0.
|
|
21400
|
+
version: "0.31.0"
|
|
21402
21401
|
},
|
|
21403
21402
|
commands: {
|
|
21404
|
-
version: "0.9.
|
|
21403
|
+
version: "0.9.3"
|
|
21405
21404
|
},
|
|
21406
21405
|
contracts: {
|
|
21407
21406
|
version: "0.9.0"
|
|
21408
21407
|
},
|
|
21409
21408
|
db: {
|
|
21410
|
-
version: "0.17.
|
|
21409
|
+
version: "0.17.1",
|
|
21411
21410
|
peerDependencies: {
|
|
21412
|
-
"drizzle-orm": "1.0.0-rc.5-
|
|
21411
|
+
"drizzle-orm": "1.0.0-rc.5-5935859"
|
|
21413
21412
|
},
|
|
21414
21413
|
devDependencies: {
|
|
21415
|
-
"drizzle-kit": "1.0.0-rc.5-
|
|
21414
|
+
"drizzle-kit": "1.0.0-rc.5-5935859"
|
|
21416
21415
|
}
|
|
21417
21416
|
},
|
|
21418
21417
|
sdk: {
|
|
21419
|
-
version: "0.
|
|
21418
|
+
version: "0.43.0",
|
|
21420
21419
|
peerDependencies: {
|
|
21421
21420
|
"@supabase/supabase-js": "^2.117.3"
|
|
21422
21421
|
}
|
|
@@ -21759,8 +21758,8 @@ function baseFiles(name, sdkDependencies) {
|
|
|
21759
21758
|
dependencies: {
|
|
21760
21759
|
"@supacloud/app": `^${appMetadata.version}`,
|
|
21761
21760
|
"@supacloud/elysia": `^${elysiaMetadata.version}`,
|
|
21762
|
-
effect: "4.0.
|
|
21763
|
-
elysia: "2.0.0-beta.
|
|
21761
|
+
effect: "4.0.5",
|
|
21762
|
+
elysia: "2.0.0-beta.32",
|
|
21764
21763
|
...sdkDependencies ?? { rxjs: appMetadata.dependencies.rxjs }
|
|
21765
21764
|
},
|
|
21766
21765
|
devDependencies: {
|
|
@@ -22958,20 +22957,21 @@ ALTER TABLE public.starter_application ENABLE ROW LEVEL SECURITY;
|
|
|
22958
22957
|
ALTER TABLE public.starter_reviews ENABLE ROW LEVEL SECURITY;
|
|
22959
22958
|
ALTER TABLE public.starter_members ENABLE ROW LEVEL SECURITY;
|
|
22960
22959
|
`;
|
|
22961
|
-
var STARTER_REVIEW_POSTGRES = `import {
|
|
22962
|
-
import { createTransactionalCommand, plaintextCommandInput } from "@supacloud/commands";
|
|
22960
|
+
var STARTER_REVIEW_POSTGRES = `import { plaintextCommandInput } from "@supacloud/commands";
|
|
22963
22961
|
import { CommandError } from "@supacloud/contracts";
|
|
22964
22962
|
import { createPostgresCommandStore, type CommandDatabase, type CommandTransaction } from "@supacloud/db";
|
|
22965
22963
|
import {
|
|
22966
|
-
ApplicationError, createSupAuthRequestContext, requireTrustedIdentity,
|
|
22964
|
+
ApplicationError, createSchemaDecoder, createSupAuthRequestContext, requireTrustedIdentity,
|
|
22965
|
+
createTransactionalCommandGovernance,
|
|
22967
22966
|
type CommandInvocation, type SupAuthContextOptions,
|
|
22968
22967
|
} from "@supacloud/elysia";
|
|
22969
22968
|
import type { AppAdapters } from "../application";
|
|
22970
22969
|
import type { ReviewStore } from "../review/review";
|
|
22971
22970
|
import type { ReviewUploadPort } from "../review/uploads";
|
|
22971
|
+
import { ApprovalInput, ReviewResult } from "../review/contracts";
|
|
22972
22972
|
|
|
22973
|
-
|
|
22974
|
-
|
|
22973
|
+
const input = createSchemaDecoder(ApprovalInput, { normalize: false });
|
|
22974
|
+
const result = createSchemaDecoder(ReviewResult, { normalize: false });
|
|
22975
22975
|
|
|
22976
22976
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
22977
22977
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -22988,19 +22988,6 @@ function isPublicConflict(value: unknown): boolean {
|
|
|
22988
22988
|
return value instanceof ApplicationError && value.status === 409
|
|
22989
22989
|
|| isRecord(value) && value["expose"] === true && value["status"] === 409 && typeof value["code"] === "string";
|
|
22990
22990
|
}
|
|
22991
|
-
function input(value: unknown): Approval {
|
|
22992
|
-
const item = record(value);
|
|
22993
|
-
if (typeof item["id"] !== "string" || !item["id"].length || typeof item["expectedVersion"] !== "number"
|
|
22994
|
-
|| !Number.isSafeInteger(item["expectedVersion"]) || item["expectedVersion"] < 1) throw new TypeError("Invalid approval input");
|
|
22995
|
-
return { id: item["id"], expectedVersion: item["expectedVersion"] };
|
|
22996
|
-
}
|
|
22997
|
-
function result(value: unknown): Review {
|
|
22998
|
-
const item = record(value);
|
|
22999
|
-
if ((item["state"] !== "draft" && item["state"] !== "approved") || typeof item["version"] !== "number"
|
|
23000
|
-
|| !Number.isSafeInteger(item["version"]) || item["version"] < 1) throw new TypeError("Invalid review result");
|
|
23001
|
-
return { state: item["state"], version: item["version"] };
|
|
23002
|
-
}
|
|
23003
|
-
|
|
23004
22991
|
export interface ReviewPostgresOptions {
|
|
23005
22992
|
database: CommandDatabase;
|
|
23006
22993
|
tenantId: string;
|
|
@@ -23038,12 +23025,7 @@ export async function createReviewPostgresAdapters(options: ReviewPostgresOption
|
|
|
23038
23025
|
} : null;
|
|
23039
23026
|
},
|
|
23040
23027
|
});
|
|
23041
|
-
const
|
|
23042
|
-
const transaction = () => {
|
|
23043
|
-
const tx = scope.getStore();
|
|
23044
|
-
if (!tx) throw new Error("Review storage requires its durable command transaction");
|
|
23045
|
-
return tx;
|
|
23046
|
-
};
|
|
23028
|
+
const transaction = () => commandBinding.transaction();
|
|
23047
23029
|
const store: ReviewStore = {
|
|
23048
23030
|
...options.uploads,
|
|
23049
23031
|
async get(table, id) {
|
|
@@ -23072,64 +23054,30 @@ export async function createReviewPostgresAdapters(options: ReviewPostgresOption
|
|
|
23072
23054
|
}
|
|
23073
23055
|
return who.subject;
|
|
23074
23056
|
}
|
|
23075
|
-
const
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23082
|
-
|
|
23083
|
-
|
|
23084
|
-
|
|
23085
|
-
|
|
23086
|
-
|
|
23087
|
-
|
|
23088
|
-
|
|
23089
|
-
|
|
23090
|
-
|
|
23091
|
-
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
"SELECT r.id FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id " +
|
|
23095
|
-
"CROSS JOIN public.starter_application a " +
|
|
23096
|
-
"WHERE r.id=$1 AND r.owner_id=$2 AND m.enabled AND m.can_approve " +
|
|
23097
|
-
"AND a.singleton AND a.project_id=$3 AND a.tenant_id=$4 FOR UPDATE OF r,m FOR SHARE OF a",
|
|
23098
|
-
[request.id, who.actorId, projectId, who.tenantId],
|
|
23099
|
-
));
|
|
23100
|
-
return found.length === 1 ? "allow" : "deny";
|
|
23101
|
-
},
|
|
23102
|
-
execute: tx => scope.run(tx, async () => {
|
|
23103
|
-
try { return await next(); }
|
|
23104
|
-
catch (error) {
|
|
23105
|
-
if (isPublicConflict(error)) {
|
|
23106
|
-
throw new CommandError("COMMAND_IDEMPOTENCY_CONFLICT");
|
|
23107
|
-
}
|
|
23108
|
-
throw error;
|
|
23109
|
-
}
|
|
23110
|
-
}),
|
|
23111
|
-
audit: { event: "review.approved", details: request => ({ reviewId: request.id }) },
|
|
23112
|
-
});
|
|
23113
|
-
try {
|
|
23114
|
-
const receipt = await command.execute(
|
|
23115
|
-
{ actorId: actor(invocation), tenantId }, requireIdempotencyKey(invocation),
|
|
23116
|
-
{ id: invocation.input.params["id"], expectedVersion: record(invocation.input.body)["expectedVersion"] },
|
|
23117
|
-
invocation.request.signal,
|
|
23118
|
-
);
|
|
23119
|
-
if (receipt.status !== "confirmed") throw new CommandError("COMMAND_OUTCOME_UNKNOWN");
|
|
23120
|
-
return receipt.result;
|
|
23121
|
-
} catch (error) {
|
|
23122
|
-
if (!(error instanceof CommandError)) throw error;
|
|
23123
|
-
const status = error.code === "COMMAND_REJECTED" ? 403
|
|
23124
|
-
: error.code === "COMMAND_IDEMPOTENCY_CONFLICT" ? 409 : 503;
|
|
23125
|
-
throw new ApplicationError(error.code, { code: error.code, status });
|
|
23126
|
-
}
|
|
23057
|
+
const commandBinding = createTransactionalCommandGovernance({
|
|
23058
|
+
permission: "review.approve",
|
|
23059
|
+
identity: invocation => ({ actorId: actor(invocation), tenantId }),
|
|
23060
|
+
input: invocation => ({
|
|
23061
|
+
...record(invocation.input.body), id: invocation.input.params["id"],
|
|
23062
|
+
}),
|
|
23063
|
+
rejection: error => isPublicConflict(error) ? new CommandError("COMMAND_IDEMPOTENCY_CONFLICT") : undefined,
|
|
23064
|
+
definition: {
|
|
23065
|
+
name: "review.approve", store: createPostgresCommandStore(database),
|
|
23066
|
+
inputCodec: plaintextCommandInput, input, result,
|
|
23067
|
+
async authorize(who, request, tx) {
|
|
23068
|
+
const found = rows(await tx.query(
|
|
23069
|
+
"SELECT r.id FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id " +
|
|
23070
|
+
"CROSS JOIN public.starter_application a " +
|
|
23071
|
+
"WHERE r.id=$1 AND r.owner_id=$2 AND m.enabled AND m.can_approve " +
|
|
23072
|
+
"AND a.singleton AND a.project_id=$3 AND a.tenant_id=$4 FOR UPDATE OF r,m FOR SHARE OF a",
|
|
23073
|
+
[request.id, who.actorId, projectId, who.tenantId],
|
|
23074
|
+
));
|
|
23075
|
+
return found.length === 1 ? "allow" : "deny";
|
|
23127
23076
|
},
|
|
23128
|
-
|
|
23129
|
-
// The durable command owns the authoritative audit and receipt in the same transaction.
|
|
23130
|
-
audit: { succeeded() { transaction(); }, failed() {} },
|
|
23077
|
+
audit: { event: "review.approved", details: request => ({ reviewId: request.id }) },
|
|
23131
23078
|
},
|
|
23132
|
-
};
|
|
23079
|
+
});
|
|
23080
|
+
return { deps: { dbClient: store }, requestContext, commandGovernance: commandBinding.governance };
|
|
23133
23081
|
}
|
|
23134
23082
|
`;
|
|
23135
23083
|
var STARTER_REVIEW_POSTGRES_TEST = `import { expect, test } from "bun:test";
|
|
@@ -23166,9 +23114,9 @@ test("PostgreSQL host never falls back to demo identity or unbound business stor
|
|
|
23166
23114
|
await expect(adapters.requestContext(new Request("http://localhost/reviews/health")))
|
|
23167
23115
|
.rejects.toMatchObject({ code: "AUTHENTICATION_REQUIRED" });
|
|
23168
23116
|
const store = adapters.deps["dbClient"] as ReviewStore;
|
|
23169
|
-
await expect(store.get("reviews", "example")).rejects.toThrow("
|
|
23117
|
+
await expect(store.get("reviews", "example")).rejects.toThrow("not active");
|
|
23170
23118
|
await expect(store.set("reviews", "example", { state: "approved", version: 2 }))
|
|
23171
|
-
.rejects.toThrow("
|
|
23119
|
+
.rejects.toThrow("not active");
|
|
23172
23120
|
expect(db.queries).toHaveLength(1);
|
|
23173
23121
|
});
|
|
23174
23122
|
`;
|
|
@@ -23454,6 +23402,58 @@ export default defineConfig({
|
|
|
23454
23402
|
};
|
|
23455
23403
|
}
|
|
23456
23404
|
|
|
23405
|
+
// src/shared/tools/app-starter-query.ts
|
|
23406
|
+
var STARTER_REVIEW_QUERY = `import type { SupabaseClient } from "@supabase/supabase-js";
|
|
23407
|
+
import type { ReviewRow } from "../../db/schema";
|
|
23408
|
+
|
|
23409
|
+
// This read projection uses SQL column names shared with the Drizzle source.
|
|
23410
|
+
// Do not expose camelCase Drizzle property names as PostgREST column names.
|
|
23411
|
+
export type ReviewSummary = Pick<ReviewRow, "id" | "state" | "version">;
|
|
23412
|
+
export type ReviewReadDatabase = {
|
|
23413
|
+
public: {
|
|
23414
|
+
Tables: {
|
|
23415
|
+
starter_reviews: {
|
|
23416
|
+
Row: ReviewSummary;
|
|
23417
|
+
Insert: never;
|
|
23418
|
+
Update: never;
|
|
23419
|
+
Relationships: [];
|
|
23420
|
+
};
|
|
23421
|
+
};
|
|
23422
|
+
Views: Record<never, never>;
|
|
23423
|
+
Functions: Record<never, never>;
|
|
23424
|
+
};
|
|
23425
|
+
};
|
|
23426
|
+
|
|
23427
|
+
export interface ReviewPageOptions {
|
|
23428
|
+
page?: number;
|
|
23429
|
+
pageSize?: number;
|
|
23430
|
+
signal?: AbortSignal;
|
|
23431
|
+
}
|
|
23432
|
+
|
|
23433
|
+
// Reuse the application's user-scoped client. Identity verification and row
|
|
23434
|
+
// authorization belong to the gateway and database, never a caller-supplied owner.
|
|
23435
|
+
export async function listReviews(
|
|
23436
|
+
client: SupabaseClient<ReviewReadDatabase>,
|
|
23437
|
+
{ page = 1, pageSize = 25, signal }: ReviewPageOptions = {},
|
|
23438
|
+
) {
|
|
23439
|
+
if (!Number.isSafeInteger(page) || page < 1
|
|
23440
|
+
|| !Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100
|
|
23441
|
+
|| !Number.isSafeInteger(page * pageSize)) {
|
|
23442
|
+
throw new RangeError("Invalid review page: page >= 1 and pageSize between 1 and 100 are required");
|
|
23443
|
+
}
|
|
23444
|
+
signal?.throwIfAborted();
|
|
23445
|
+
const start = (page - 1) * pageSize;
|
|
23446
|
+
const query = client.from("starter_reviews")
|
|
23447
|
+
.select("id,state,version", { count: "exact" })
|
|
23448
|
+
.order("id", { ascending: true })
|
|
23449
|
+
.range(start, start + pageSize - 1);
|
|
23450
|
+
const { data, error, count } = await (signal ? query.abortSignal(signal) : query);
|
|
23451
|
+
if (error) throw error;
|
|
23452
|
+
if (data === null || count === null) throw new Error("Incomplete review page response");
|
|
23453
|
+
return { data, total: count, page, pageSize, hasNextPage: start + data.length < count };
|
|
23454
|
+
}
|
|
23455
|
+
`;
|
|
23456
|
+
|
|
23457
23457
|
// src/shared/tools/app-starter.ts
|
|
23458
23458
|
var { compiler: compilerMetadata2, app: appMetadata2, elysia: elysiaMetadata2, commands: commandsMetadata, contracts: contractsMetadata, db: dbMetadata, sdk: sdkMetadata } = starter_metadata_default.packages;
|
|
23459
23459
|
var json2 = (value) => `${JSON.stringify(value, null, 2)}
|
|
@@ -23461,6 +23461,7 @@ var json2 = (value) => `${JSON.stringify(value, null, 2)}
|
|
|
23461
23461
|
function appStarterFiles(name) {
|
|
23462
23462
|
return {
|
|
23463
23463
|
...starterDatabaseFiles(),
|
|
23464
|
+
"src/review/list-reviews.ts": STARTER_REVIEW_QUERY,
|
|
23464
23465
|
"package.json": json2({
|
|
23465
23466
|
name,
|
|
23466
23467
|
version: "0.0.0",
|
|
@@ -23495,8 +23496,8 @@ function appStarterFiles(name) {
|
|
|
23495
23496
|
"drizzle-orm": dbMetadata.peerDependencies["drizzle-orm"],
|
|
23496
23497
|
"@supacloud/js": `^${sdkMetadata.version}`,
|
|
23497
23498
|
"@supabase/supabase-js": sdkMetadata.peerDependencies["@supabase/supabase-js"],
|
|
23498
|
-
effect: "4.0.
|
|
23499
|
-
elysia: "2.0.0-beta.
|
|
23499
|
+
effect: "4.0.5",
|
|
23500
|
+
elysia: "2.0.0-beta.32",
|
|
23500
23501
|
rxjs: appMetadata2.dependencies.rxjs
|
|
23501
23502
|
},
|
|
23502
23503
|
devDependencies: {
|
|
@@ -23750,11 +23751,10 @@ import {
|
|
|
23750
23751
|
Body, Command, Controller, DB_CLIENT, Get, Inject, Param, Post,
|
|
23751
23752
|
defineFeatureSlice, defineFeatureSpec, type Aspect,
|
|
23752
23753
|
} from "@supacloud/app";
|
|
23753
|
-
import { ApplicationError, assertFeatureTransition } from "@supacloud/elysia";
|
|
23754
|
-
import { ApproveBody, HealthResult, Params, ReviewResult } from "./contracts";
|
|
23754
|
+
import { ApplicationError, assertFeatureTransition, createSchemaDecoder } from "@supacloud/elysia";
|
|
23755
|
+
import { ApproveBody, HealthResult, Params, ReviewResult, type ApprovalBody, type Review } from "./contracts";
|
|
23755
23756
|
import { VerifyReviewAttachment } from "./attachment";
|
|
23756
23757
|
import { ReviewUploads, ReviewUploadsController, type ReviewUploadPort } from "./uploads";
|
|
23757
|
-
import type { ReviewRow } from "../../db/schema";
|
|
23758
23758
|
|
|
23759
23759
|
export const reviewSpec = defineFeatureSpec({
|
|
23760
23760
|
name: "review",
|
|
@@ -23769,7 +23769,6 @@ export const reviewSpec = defineFeatureSpec({
|
|
|
23769
23769
|
},
|
|
23770
23770
|
});
|
|
23771
23771
|
|
|
23772
|
-
type Review = Pick<ReviewRow, "state" | "version">;
|
|
23773
23772
|
// Production implementations must bind both operations to the current transaction.
|
|
23774
23773
|
export interface ReviewStore extends Partial<ReviewUploadPort> {
|
|
23775
23774
|
get(table: string, key: string): unknown;
|
|
@@ -23782,13 +23781,12 @@ export const requireRequest: Aspect = (context, next) => {
|
|
|
23782
23781
|
return next();
|
|
23783
23782
|
};
|
|
23784
23783
|
|
|
23784
|
+
const decodeReview = createSchemaDecoder(ReviewResult, { normalize: false });
|
|
23785
23785
|
function readReview(value: unknown): Review {
|
|
23786
|
-
|
|
23787
|
-
|
|
23788
|
-
!("version" in value) || typeof value.version !== "number") {
|
|
23786
|
+
try { return decodeReview(value); }
|
|
23787
|
+
catch {
|
|
23789
23788
|
throw new ApplicationError("Review not found", { status: 404, code: "REVIEW_NOT_FOUND" });
|
|
23790
23789
|
}
|
|
23791
|
-
return { state: value.state, version: value.version };
|
|
23792
23790
|
}
|
|
23793
23791
|
|
|
23794
23792
|
@Command({
|
|
@@ -23820,7 +23818,7 @@ type ReviewError =
|
|
|
23820
23818
|
| { readonly _tag: "FEATURE_TRANSITION_CONFLICT" }
|
|
23821
23819
|
| { readonly _tag: "COMMAND_IDEMPOTENCY_CONFLICT" }
|
|
23822
23820
|
| { readonly _tag: "COMMAND_REJECTED" }
|
|
23823
|
-
| { readonly _tag: "
|
|
23821
|
+
| { readonly _tag: "COMMAND_BINDING_MISMATCH" }
|
|
23824
23822
|
| { readonly _tag: "COMMAND_OUTCOME_UNKNOWN" };
|
|
23825
23823
|
|
|
23826
23824
|
@Controller("/reviews")
|
|
@@ -23849,13 +23847,13 @@ export class ReviewController {
|
|
|
23849
23847
|
{ tag: "FEATURE_TRANSITION_CONFLICT", status: 409, code: "FEATURE_TRANSITION_CONFLICT" },
|
|
23850
23848
|
{ tag: "COMMAND_IDEMPOTENCY_CONFLICT", status: 409, code: "COMMAND_IDEMPOTENCY_CONFLICT" },
|
|
23851
23849
|
{ tag: "COMMAND_REJECTED", status: 403, code: "COMMAND_REJECTED" },
|
|
23852
|
-
{ tag: "
|
|
23850
|
+
{ tag: "COMMAND_BINDING_MISMATCH", status: 501, code: "COMMAND_BINDING_MISMATCH" },
|
|
23853
23851
|
{ tag: "COMMAND_OUTCOME_UNKNOWN", status: 503, code: "COMMAND_OUTCOME_UNKNOWN" },
|
|
23854
23852
|
],
|
|
23855
23853
|
retry: "none",
|
|
23856
23854
|
},
|
|
23857
23855
|
})
|
|
23858
|
-
approve(@Param("id") id: string, @Body() body:
|
|
23856
|
+
approve(@Param("id") id: string, @Body() body: ApprovalBody): Effect.Effect<Review, ReviewError, never> {
|
|
23859
23857
|
return Effect.promise(() => this.approveReview.execute(id, body.expectedVersion));
|
|
23860
23858
|
}
|
|
23861
23859
|
}
|
|
@@ -23867,13 +23865,16 @@ export const ReviewFeature = defineFeatureSlice({
|
|
|
23867
23865
|
});
|
|
23868
23866
|
`,
|
|
23869
23867
|
"src/review/attachment.ts": STARTER_REVIEW_JOB,
|
|
23870
|
-
"src/review/contracts.ts": `import { t } from "elysia";
|
|
23868
|
+
"src/review/contracts.ts": `import { t, type UnwrapSchema } from "elysia";
|
|
23871
23869
|
export const Params = t.Object({ id: t.String({ minLength: 1 }) });
|
|
23872
|
-
export const ApproveBody = t.Object({ expectedVersion: t.Integer({ minimum: 1 }) });
|
|
23870
|
+
export const ApproveBody = t.Object({ expectedVersion: t.Integer({ minimum: 1, maximum: 9007199254740991 }) });
|
|
23871
|
+
export const ApprovalInput = t.Object({ ...Params.properties, ...ApproveBody.properties }, { additionalProperties: false });
|
|
23872
|
+
export type ApprovalBody = UnwrapSchema<typeof ApproveBody>;
|
|
23873
23873
|
export const ReviewResult = t.Object({
|
|
23874
23874
|
state: t.Union([t.Literal("draft"), t.Literal("approved")]),
|
|
23875
|
-
version: t.Integer({ minimum: 1 }),
|
|
23875
|
+
version: t.Integer({ minimum: 1, maximum: 9007199254740991 }),
|
|
23876
23876
|
});
|
|
23877
|
+
export type Review = UnwrapSchema<typeof ReviewResult>;
|
|
23877
23878
|
export const HealthResult = t.Object({ ok: t.Boolean() });
|
|
23878
23879
|
export const UploadParams = t.Object({ id: t.String({ format: "uuid" }) });
|
|
23879
23880
|
export const UploadBody = t.Object({
|
|
@@ -24015,6 +24016,18 @@ Ordinary reads use PostgREST through the existing user-scoped
|
|
|
24015
24016
|
Use the caller's session, RLS and grants; never introduce a second session store.
|
|
24016
24017
|
Business writes continue through the governed Command/RPC boundary.
|
|
24017
24018
|
|
|
24019
|
+
Use listReviews from src/review/list-reviews.ts for the paginated review list.
|
|
24020
|
+
Type the existing client with ReviewReadDatabase (a read-only projection derived
|
|
24021
|
+
from db/schema.ts), then call listReviews(client, { page: 1, pageSize: 25, signal }).
|
|
24022
|
+
The helper returns data, total, page, pageSize and hasNextPage, orders by the unique
|
|
24023
|
+
id and preserves PostgREST errors. Pages are one-based and limited to 100 rows.
|
|
24024
|
+
Exact counts obey RLS but may be expensive on large tables; offset pages are not
|
|
24025
|
+
a cross-request snapshot when records change. No owner filter replaces RLS.
|
|
24026
|
+
Do not create a service-role client or a second auth session for this helper.
|
|
24027
|
+
Existing starters can add this file without a new migration only if their reviewed
|
|
24028
|
+
review-table grants and identity-mapping RLS from migration 003 are already applied.
|
|
24029
|
+
The projection is not a full database export and grants no write access.
|
|
24030
|
+
|
|
24018
24031
|
GraphQL is not generated or enabled by this starter. For a specific Query Contract,
|
|
24019
24032
|
explicitly configure graphql.schema with a role-scoped database export and add
|
|
24020
24033
|
checked query documents. Drizzle and append-only migrations remain the database
|
|
@@ -30513,7 +30526,7 @@ async function runProjectDeployment(http, args) {
|
|
|
30513
30526
|
// package.json
|
|
30514
30527
|
var package_default = {
|
|
30515
30528
|
name: "@supacloud/cli",
|
|
30516
|
-
version: "0.
|
|
30529
|
+
version: "0.101.0",
|
|
30517
30530
|
description: "Project-scoped CLI for SupaCloud users",
|
|
30518
30531
|
type: "module",
|
|
30519
30532
|
main: "./dist/index.js",
|
|
@@ -30565,21 +30578,21 @@ var package_default = {
|
|
|
30565
30578
|
directory: "packages/cli"
|
|
30566
30579
|
},
|
|
30567
30580
|
dependencies: {
|
|
30568
|
-
"@supacloud/compiler": "^0.46.
|
|
30569
|
-
"@supacloud/delivery": "0.18.
|
|
30570
|
-
"@supacloud/db": "0.17.
|
|
30571
|
-
typebox: "1.3.
|
|
30581
|
+
"@supacloud/compiler": "^0.46.3",
|
|
30582
|
+
"@supacloud/delivery": "0.18.1",
|
|
30583
|
+
"@supacloud/db": "0.17.1",
|
|
30584
|
+
typebox: "1.3.37"
|
|
30572
30585
|
},
|
|
30573
30586
|
overrides: {
|
|
30574
30587
|
"@supacloud/contracts": "0.9.0",
|
|
30575
|
-
"@supacloud/delivery": "0.18.
|
|
30576
|
-
"@graphql-tools/utils": "^12.0.
|
|
30588
|
+
"@supacloud/delivery": "0.18.1",
|
|
30589
|
+
"@graphql-tools/utils": "^12.0.4"
|
|
30577
30590
|
},
|
|
30578
30591
|
devDependencies: {
|
|
30579
|
-
"@supacloud/elysia": "
|
|
30592
|
+
"@supacloud/elysia": "0.31.0",
|
|
30580
30593
|
"@types/bun": "^1.4.3",
|
|
30581
|
-
effect: "4.0.
|
|
30582
|
-
elysia: "2.0.0-beta.
|
|
30594
|
+
effect: "4.0.5",
|
|
30595
|
+
elysia: "2.0.0-beta.32",
|
|
30583
30596
|
typescript: "^7.0.2"
|
|
30584
30597
|
}
|
|
30585
30598
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const STARTER_REVIEW_SCHEMA = "-- Apply with the platform migration owner, never from the HTTP process.\n-- One application database belongs to one explicitly provisioned project/tenant.\nCREATE TABLE public.starter_application (\n singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),\n project_id text NOT NULL CHECK (length(project_id) > 0),\n tenant_id text NOT NULL CHECK (length(tenant_id) > 0)\n);\nCREATE TABLE public.starter_reviews (\n id text PRIMARY KEY, owner_id text NOT NULL,\n state text NOT NULL CHECK (state IN ('draft','approved')),\n version integer NOT NULL CHECK (version > 0)\n);\nCREATE TABLE public.starter_members (\n subject text PRIMARY KEY, enabled boolean NOT NULL DEFAULT true,\n can_approve boolean NOT NULL DEFAULT false\n);\nALTER TABLE public.starter_application ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.starter_reviews ENABLE ROW LEVEL SECURITY;\nALTER TABLE public.starter_members ENABLE ROW LEVEL SECURITY;\n";
|
|
2
|
-
export declare const STARTER_REVIEW_POSTGRES = "import {
|
|
3
|
-
export declare const STARTER_REVIEW_POSTGRES_TEST = "import { expect, test } from \"bun:test\";\nimport { createReviewPostgresAdapters } from \"../src/host/review-postgres\";\nimport type { ReviewStore } from \"../src/review/review\";\n\nconst identity = {\n issuer: \"https://identity.example.test\", audience: \"review-test\", clientId: \"review-client\",\n projectId: \"review-test\", jwksUrl: \"https://identity.example.test/jwks\",\n};\nfunction database(binding: unknown) {\n const queries: string[] = [];\n return {\n queries,\n async transaction<T>(run: (tx: { query(text: string): Promise<unknown> }) => Promise<T>): Promise<T> {\n return run({ async query(text) { queries.push(text); return binding; } });\n },\n };\n}\n\ntest(\"PostgreSQL host rejects missing or mismatched application ownership without creating data\", async () => {\n for (const binding of [[], [{ project_id: \"other\", tenant_id: \"review-test\" }],\n [{ project_id: \"review-test\", tenant_id: \"other\" }]]) {\n const db = database(binding);\n await expect(createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" }))\n .rejects.toThrow(\"binding mismatch\");\n expect(db.queries).toEqual([\"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\"]);\n }\n});\n\ntest(\"PostgreSQL host never falls back to demo identity or unbound business storage\", async () => {\n const db = database([{ project_id: \"review-test\", tenant_id: \"review-test\" }]);\n const adapters = await createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" });\n await expect(adapters.requestContext(new Request(\"http://localhost/reviews/health\")))\n .rejects.toMatchObject({ code: \"AUTHENTICATION_REQUIRED\" });\n const store = adapters.deps[\"dbClient\"] as ReviewStore;\n await expect(store.get(\"reviews\", \"example\")).rejects.toThrow(\"
|
|
2
|
+
export declare const STARTER_REVIEW_POSTGRES = "import { plaintextCommandInput } from \"@supacloud/commands\";\nimport { CommandError } from \"@supacloud/contracts\";\nimport { createPostgresCommandStore, type CommandDatabase, type CommandTransaction } from \"@supacloud/db\";\nimport {\n ApplicationError, createSchemaDecoder, createSupAuthRequestContext, requireTrustedIdentity,\n createTransactionalCommandGovernance,\n type CommandInvocation, type SupAuthContextOptions,\n} from \"@supacloud/elysia\";\nimport type { AppAdapters } from \"../application\";\nimport type { ReviewStore } from \"../review/review\";\nimport type { ReviewUploadPort } from \"../review/uploads\";\nimport { ApprovalInput, ReviewResult } from \"../review/contracts\";\n\nconst input = createSchemaDecoder(ApprovalInput, { normalize: false });\nconst result = createSchemaDecoder(ReviewResult, { normalize: false });\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction record(value: unknown): Record<string, unknown> {\n if (!isRecord(value)) throw new TypeError(\"Invalid review record\");\n return value;\n}\nfunction rows(value: unknown): Record<string, unknown>[] {\n if (!Array.isArray(value)) throw new TypeError(\"Invalid database result\");\n return value.map(record);\n}\nfunction isPublicConflict(value: unknown): boolean {\n return value instanceof ApplicationError && value.status === 409\n || isRecord(value) && value[\"expose\"] === true && value[\"status\"] === 409 && typeof value[\"code\"] === \"string\";\n}\nexport interface ReviewPostgresOptions {\n database: CommandDatabase;\n tenantId: string;\n identity: Omit<SupAuthContextOptions, \"resolveAccess\">;\n uploads?: ReviewUploadPort;\n // Only database effects here: they commit/rollback with the approval and receipt.\n afterApproved?(tx: CommandTransaction, id: string, version: number): Promise<void>;\n}\n\nexport async function createReviewPostgresAdapters(options: ReviewPostgresOptions): Promise<\n AppAdapters & { requestContext: ReturnType<typeof createSupAuthRequestContext> }\n> {\n const { database, tenantId, identity } = options;\n const projectId = identity.projectId;\n const afterApproved = options.afterApproved;\n if (!tenantId.trim()) throw new TypeError(\"Review tenantId is required\");\n const query = (text: string, parameters: readonly string[] = []) =>\n database.transaction(tx => tx.query(text, parameters));\n const binding = rows(await query(\n \"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\",\n ));\n if (binding.length !== 1 || binding[0]?.[\"project_id\"] !== projectId || binding[0]?.[\"tenant_id\"] !== tenantId) {\n throw new Error(\"Review database project/tenant binding mismatch\");\n }\n const requestContext = createSupAuthRequestContext({\n ...identity,\n async resolveAccess(who) {\n const found = rows(await query(\n \"SELECT m.can_approve FROM public.starter_members m,public.starter_application a \" +\n \"WHERE m.subject=$1 AND m.enabled AND a.singleton AND a.project_id=$2 AND a.tenant_id=$3\",\n [who.subject, projectId, tenantId],\n ));\n return found.length === 1 ? { projectId, tenantId,\n permissions: found[0]?.[\"can_approve\"] === true ? [\"review.approve\"] : [],\n } : null;\n },\n });\n const transaction = () => commandBinding.transaction();\n const store: ReviewStore = {\n ...options.uploads,\n async get(table, id) {\n if (table !== \"reviews\") throw new Error(\"Unsupported review table\");\n return rows(await transaction().query(\n \"SELECT state,version FROM public.starter_reviews WHERE id=$1 FOR UPDATE\", [id],\n ))[0];\n },\n async set(table, id, value) {\n if (table !== \"reviews\") throw new Error(\"Unsupported review table\");\n const review = result(value);\n const updated = rows(await transaction().query(\n \"UPDATE public.starter_reviews SET state=$2,version=$3 WHERE id=$1 RETURNING id\",\n [id, review.state, review.version],\n ));\n if (updated.length !== 1) throw new Error(\"Review write did not update one record\");\n await afterApproved?.(transaction(), id, review.version);\n },\n };\n function actor(invocation: CommandInvocation): string {\n const who = requireTrustedIdentity(invocation.requestContext);\n const access = record(record(invocation.requestContext)[\"access\"]);\n if (access[\"projectId\"] !== projectId || access[\"tenantId\"] !== tenantId\n || !Array.isArray(access[\"permissions\"]) || !access[\"permissions\"].includes(\"review.approve\")) {\n throw new ApplicationError(\"Command permission denied\", { status: 403, code: \"REVIEW_PERMISSION_DENIED\" });\n }\n return who.subject;\n }\n const commandBinding = createTransactionalCommandGovernance({\n permission: \"review.approve\",\n identity: invocation => ({ actorId: actor(invocation), tenantId }),\n input: invocation => ({\n ...record(invocation.input.body), id: invocation.input.params[\"id\"],\n }),\n rejection: error => isPublicConflict(error) ? new CommandError(\"COMMAND_IDEMPOTENCY_CONFLICT\") : undefined,\n definition: {\n name: \"review.approve\", store: createPostgresCommandStore(database),\n inputCodec: plaintextCommandInput, input, result,\n async authorize(who, request, tx) {\n const found = rows(await tx.query(\n \"SELECT r.id FROM public.starter_reviews r JOIN public.starter_members m ON m.subject=r.owner_id \" +\n \"CROSS JOIN public.starter_application a \" +\n \"WHERE r.id=$1 AND r.owner_id=$2 AND m.enabled AND m.can_approve \" +\n \"AND a.singleton AND a.project_id=$3 AND a.tenant_id=$4 FOR UPDATE OF r,m FOR SHARE OF a\",\n [request.id, who.actorId, projectId, who.tenantId],\n ));\n return found.length === 1 ? \"allow\" : \"deny\";\n },\n audit: { event: \"review.approved\", details: request => ({ reviewId: request.id }) },\n },\n });\n return { deps: { dbClient: store }, requestContext, commandGovernance: commandBinding.governance };\n}\n";
|
|
3
|
+
export declare const STARTER_REVIEW_POSTGRES_TEST = "import { expect, test } from \"bun:test\";\nimport { createReviewPostgresAdapters } from \"../src/host/review-postgres\";\nimport type { ReviewStore } from \"../src/review/review\";\n\nconst identity = {\n issuer: \"https://identity.example.test\", audience: \"review-test\", clientId: \"review-client\",\n projectId: \"review-test\", jwksUrl: \"https://identity.example.test/jwks\",\n};\nfunction database(binding: unknown) {\n const queries: string[] = [];\n return {\n queries,\n async transaction<T>(run: (tx: { query(text: string): Promise<unknown> }) => Promise<T>): Promise<T> {\n return run({ async query(text) { queries.push(text); return binding; } });\n },\n };\n}\n\ntest(\"PostgreSQL host rejects missing or mismatched application ownership without creating data\", async () => {\n for (const binding of [[], [{ project_id: \"other\", tenant_id: \"review-test\" }],\n [{ project_id: \"review-test\", tenant_id: \"other\" }]]) {\n const db = database(binding);\n await expect(createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" }))\n .rejects.toThrow(\"binding mismatch\");\n expect(db.queries).toEqual([\"SELECT project_id,tenant_id FROM public.starter_application WHERE singleton\"]);\n }\n});\n\ntest(\"PostgreSQL host never falls back to demo identity or unbound business storage\", async () => {\n const db = database([{ project_id: \"review-test\", tenant_id: \"review-test\" }]);\n const adapters = await createReviewPostgresAdapters({ database: db, identity, tenantId: \"review-test\" });\n await expect(adapters.requestContext(new Request(\"http://localhost/reviews/health\")))\n .rejects.toMatchObject({ code: \"AUTHENTICATION_REQUIRED\" });\n const store = adapters.deps[\"dbClient\"] as ReviewStore;\n await expect(store.get(\"reviews\", \"example\")).rejects.toThrow(\"not active\");\n await expect(store.set(\"reviews\", \"example\", { state: \"approved\", version: 2 }))\n .rejects.toThrow(\"not active\");\n expect(db.queries).toHaveLength(1);\n});\n";
|
|
4
4
|
export declare const STARTER_REVIEW_DELIVERY_HOST = "import { SQL } from \"bun\";\nimport { createClient } from \"@supabase/supabase-js\";\nimport { createBunCommandDatabase } from \"@supacloud/db/bun\";\nimport { createApplication, type CompiledModule } from \"@supacloud/elysia\";\nimport { createReviewPostgresAdapters } from \"./host/review-postgres\";\nimport { createReviewAttachmentAdapters } from \"./host/review-attachments\";\nimport { createReviewUploadAdapters } from \"./host/review-uploads\";\n\nfunction required(name: string): string {\n const value = process.env[name];\n if (!value?.trim()) throw new Error(\"Missing required host setting: \" + name);\n return value;\n}\n\nfunction databaseConnection(): SQL.Options {\n const socket = process.env[\"DATABASE_SOCKET_PATH\"];\n if (socket && process.env[\"DATABASE_URL\"]) throw new Error(\"Conflicting database connection settings\");\n if (socket) {\n return { adapter: \"postgres\", path: socket, database: required(\"DATABASE_NAME\"), username: required(\"DATABASE_USER\") };\n }\n const url = new URL(required(\"DATABASE_URL\"));\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") throw new Error(\"Invalid database URL\");\n return {\n adapter: \"postgres\", url: url.href, hostname: url.hostname.replace(/^\\[|\\]$/g, \"\"), port: Number(url.port || 5432),\n username: decodeURIComponent(url.username), password: decodeURIComponent(url.password),\n database: decodeURIComponent(url.pathname.slice(1)),\n };\n}\n\nexport async function createDeliveryApplication(modules: CompiledModule[], lifecycle: { signal: AbortSignal }) {\n lifecycle.signal.throwIfAborted();\n const connection = databaseConnection();\n const attachments = process.env[\"REVIEW_ATTACHMENTS\"];\n if (attachments !== undefined && attachments !== \"enabled\") throw new Error(\"Invalid attachment configuration\");\n const settings = {\n tenantId: required(\"APP_TENANT_ID\"),\n identity: {\n issuer: required(\"SUPAUTH_ISSUER\"), audience: required(\"SUPAUTH_AUDIENCE\"),\n clientId: required(\"SUPAUTH_CLIENT_ID\"), projectId: required(\"SUPACLOUD_PROJECT_ID\"),\n jwksUrl: required(\"SUPAUTH_JWKS_URL\"),\n },\n };\n const pool = new SQL({ ...connection, max: 12, connectionTimeout: 5 });\n let closing: Promise<void> | undefined;\n const close = () => closing ??= pool.close({ timeout: 5 });\n let stage: \"attachments\" | \"uploads\" | \"postgres\" | \"application\" = \"attachments\";\n try {\n const database = createBunCommandDatabase(pool);\n const attachmentOptions = attachments === \"enabled\" ? {\n database, tenantId: settings.tenantId, projectId: settings.identity.projectId,\n service: createClient(required(\"SUPACLOUD_URL\"), required(\"SUPACLOUD_SERVICE_ROLE_KEY\"), {\n auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },\n }),\n } : undefined;\n const durable = attachmentOptions ? await createReviewAttachmentAdapters(attachmentOptions) : undefined;\n stage = \"uploads\";\n const uploads = attachmentOptions ? await createReviewUploadAdapters(attachmentOptions) : undefined;\n stage = \"postgres\";\n const adapters = await createReviewPostgresAdapters({\n database, tenantId: settings.tenantId, identity: settings.identity,\n ...(uploads ? { uploads } : {}),\n ...(durable ? { afterApproved: durable.enqueue } : {}),\n });\n lifecycle.signal.throwIfAborted();\n stage = \"application\";\n const app = createApplication({ name: \"review\", modules, ...adapters });\n return { fetch: (request: Request) => app.handle(request), close };\n } catch (error) {\n await close();\n const code = error !== null && typeof error === \"object\" && \"code\" in error\n && typeof error.code === \"string\" && /^[A-Z0-9_]{1,64}$/.test(error.code) ? error.code : \"UNKNOWN\";\n console.error(JSON.stringify({ event: \"review-host-initialization-failed\", stage, code }));\n // Database/identity errors can contain connection details; never echo the underlying cause.\n throw new Error(\"Review host initialization failed\");\n }\n}\n";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const STARTER_REVIEW_QUERY = "import type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { ReviewRow } from \"../../db/schema\";\n\n// This read projection uses SQL column names shared with the Drizzle source.\n// Do not expose camelCase Drizzle property names as PostgREST column names.\nexport type ReviewSummary = Pick<ReviewRow, \"id\" | \"state\" | \"version\">;\nexport type ReviewReadDatabase = {\n public: {\n Tables: {\n starter_reviews: {\n Row: ReviewSummary;\n Insert: never;\n Update: never;\n Relationships: [];\n };\n };\n Views: Record<never, never>;\n Functions: Record<never, never>;\n };\n};\n\nexport interface ReviewPageOptions {\n page?: number;\n pageSize?: number;\n signal?: AbortSignal;\n}\n\n// Reuse the application's user-scoped client. Identity verification and row\n// authorization belong to the gateway and database, never a caller-supplied owner.\nexport async function listReviews(\n client: SupabaseClient<ReviewReadDatabase>,\n { page = 1, pageSize = 25, signal }: ReviewPageOptions = {},\n) {\n if (!Number.isSafeInteger(page) || page < 1\n || !Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 100\n || !Number.isSafeInteger(page * pageSize)) {\n throw new RangeError(\"Invalid review page: page >= 1 and pageSize between 1 and 100 are required\");\n }\n signal?.throwIfAborted();\n const start = (page - 1) * pageSize;\n const query = client.from(\"starter_reviews\")\n .select(\"id,state,version\", { count: \"exact\" })\n .order(\"id\", { ascending: true })\n .range(start, start + pageSize - 1);\n const { data, error, count } = await (signal ? query.abortSignal(signal) : query);\n if (error) throw error;\n if (data === null || count === null) throw new Error(\"Incomplete review page response\");\n return { data, total: count, page, pageSize, hasNextPage: start + data.length < count };\n}\n";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.101.0",
|
|
4
4
|
"description": "Project-scoped CLI for SupaCloud users",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,21 +52,21 @@
|
|
|
52
52
|
"directory": "packages/cli"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@supacloud/compiler": "^0.46.
|
|
56
|
-
"@supacloud/delivery": "0.18.
|
|
57
|
-
"@supacloud/db": "0.17.
|
|
58
|
-
"typebox": "1.3.
|
|
55
|
+
"@supacloud/compiler": "^0.46.3",
|
|
56
|
+
"@supacloud/delivery": "0.18.1",
|
|
57
|
+
"@supacloud/db": "0.17.1",
|
|
58
|
+
"typebox": "1.3.37"
|
|
59
59
|
},
|
|
60
60
|
"overrides": {
|
|
61
61
|
"@supacloud/contracts": "0.9.0",
|
|
62
|
-
"@supacloud/delivery": "0.18.
|
|
63
|
-
"@graphql-tools/utils": "^12.0.
|
|
62
|
+
"@supacloud/delivery": "0.18.1",
|
|
63
|
+
"@graphql-tools/utils": "^12.0.4"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@supacloud/elysia": "
|
|
66
|
+
"@supacloud/elysia": "0.31.0",
|
|
67
67
|
"@types/bun": "^1.4.3",
|
|
68
|
-
"effect": "4.0.
|
|
69
|
-
"elysia": "2.0.0-beta.
|
|
68
|
+
"effect": "4.0.5",
|
|
69
|
+
"elysia": "2.0.0-beta.32",
|
|
70
70
|
"typescript": "^7.0.2"
|
|
71
71
|
}
|
|
72
72
|
}
|