@supacloud/cli 0.103.3 → 0.103.5
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 +10 -0
- package/dist/index.js +470 -209
- package/dist/shared/tools/app-starter-query.d.ts +1 -1
- package/dist/shared/tools/app-starter-review-job.d.ts +1 -1
- package/dist/shared/tools/app-tools.d.ts +5 -0
- package/dist/shared/tools/edge-function-response.d.ts +2 -0
- package/dist/shared/tools/function-aot.d.ts +9 -0
- package/package.json +7 -7
|
@@ -1 +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";
|
|
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 if (!Array.isArray(data)\n || !Number.isSafeInteger(count) || count < 0\n || data.length > pageSize\n || (data.length > 0 && start + data.length > count)) {\n throw new Error(\"Invalid review page response\");\n }\n return { data, total: count, page, pageSize, hasNextPage: start + data.length < count };\n}\n";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const STARTER_REVIEW_JOB = "import { DB_CLIENT, Inject, Injectable, Job } from \"@supacloud/app\";\nimport { ApplicationError } from \"@supacloud/elysia\";\nimport { t } from \"elysia\";\nimport type { Static } from \"elysia/type\";\n\nexport const AttachmentInput = t.Object({\n reviewId: t.String({ minLength: 1, maxLength: 100 }),\n version: t.Integer({ minimum: 2 }),\n artifactId: t.String({ format: \"uuid\" }),\n}, { additionalProperties: false });\n\nexport const AttachmentResult = t.Object({\n ...AttachmentInput.properties,\n sha256: t.String({ pattern: \"^[a-f0-9]{64}$\" }),\n bytes: t.Integer({ minimum: 1, maximum: 1048576 }),\n}, { additionalProperties: false });\n\nexport type ReviewAttachmentInput = Static<typeof AttachmentInput>;\nexport type ReviewAttachmentResult = Static<typeof AttachmentResult>;\n\nexport interface ReviewAttachmentStore {\n // Reauthorize before returning a committed result; null means execution is still needed.\n readResult(input: ReviewAttachmentInput): Promise<ReviewAttachmentResult | null>;\n // Resolve the immutable artifact under the worker's own authorization and current review revision.\n readAttachment(input: ReviewAttachmentInput): Promise<{ body: Uint8Array; sha256: string }>;\n // Revalidate revision/ownership and persist idempotently in a transaction; return the durable result.\n recordAttachment(input: ReviewAttachmentInput, result: ReviewAttachmentResult): Promise<ReviewAttachmentResult>;\n}\n\n@Injectable({ scope: \"job\" })\n@Job({\n name: \"review.verify-attachment\", mode: \"task\", idempotency: \"required\",\n timeoutSec: 30, maxAttempts: 3, input: AttachmentInput, output: AttachmentResult,\n})\nexport class VerifyReviewAttachment {\n constructor(@Inject(DB_CLIENT) private readonly store: ReviewAttachmentStore) {}\n\n async run(input: ReviewAttachmentInput): Promise<ReviewAttachmentResult> {\n const committed = await this.store.readResult(input);\n if (committed !== null) return committed;\n const source = await this.store.readAttachment(input);\n if (source.body.byteLength < 1 || source.body.byteLength > 1048576) {\n throw new ApplicationError(\"Attachment size is outside the supported range\", { code: \"ATTACHMENT_SIZE_INVALID\" });\n }\n const digest = await crypto.subtle.digest(\"SHA-256\", new Uint8Array(source.body));\n const sha256 = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n if (sha256 !== source.sha256) {\n throw new ApplicationError(\"Attachment integrity check failed\", { code: \"ATTACHMENT_INTEGRITY_FAILED\" });\n }\n return this.store.recordAttachment(input, { ...input, sha256, bytes: source.body.byteLength });\n }\n}\n";
|
|
1
|
+
export declare const STARTER_REVIEW_JOB = "import { DB_CLIENT, Inject, Injectable, Job } from \"@supacloud/app\";\nimport { ApplicationError } from \"@supacloud/elysia\";\nimport { t } from \"elysia\";\nimport type { Static } from \"elysia/type\";\nimport { REVIEW_VERSION_MAX } from \"./limits\";\n\nexport const AttachmentInput = t.Object({\n reviewId: t.String({ minLength: 1, maxLength: 100 }),\n version: t.Integer({ minimum: 2, maximum: REVIEW_VERSION_MAX }),\n artifactId: t.String({ format: \"uuid\" }),\n}, { additionalProperties: false });\n\nexport const AttachmentResult = t.Object({\n ...AttachmentInput.properties,\n sha256: t.String({ pattern: \"^[a-f0-9]{64}$\" }),\n bytes: t.Integer({ minimum: 1, maximum: 1048576 }),\n}, { additionalProperties: false });\n\nexport type ReviewAttachmentInput = Static<typeof AttachmentInput>;\nexport type ReviewAttachmentResult = Static<typeof AttachmentResult>;\n\nexport interface ReviewAttachmentStore {\n // Reauthorize before returning a committed result; null means execution is still needed.\n readResult(input: ReviewAttachmentInput): Promise<ReviewAttachmentResult | null>;\n // Resolve the immutable artifact under the worker's own authorization and current review revision.\n readAttachment(input: ReviewAttachmentInput): Promise<{ body: Uint8Array; sha256: string }>;\n // Revalidate revision/ownership and persist idempotently in a transaction; return the durable result.\n recordAttachment(input: ReviewAttachmentInput, result: ReviewAttachmentResult): Promise<ReviewAttachmentResult>;\n}\n\n@Injectable({ scope: \"job\" })\n@Job({\n name: \"review.verify-attachment\", mode: \"task\", idempotency: \"required\",\n timeoutSec: 30, maxAttempts: 3, input: AttachmentInput, output: AttachmentResult,\n})\nexport class VerifyReviewAttachment {\n constructor(@Inject(DB_CLIENT) private readonly store: ReviewAttachmentStore) {}\n\n async run(input: ReviewAttachmentInput): Promise<ReviewAttachmentResult> {\n const committed = await this.store.readResult(input);\n if (committed !== null) return committed;\n const source = await this.store.readAttachment(input);\n if (source.body.byteLength < 1 || source.body.byteLength > 1048576) {\n throw new ApplicationError(\"Attachment size is outside the supported range\", { code: \"ATTACHMENT_SIZE_INVALID\" });\n }\n const digest = await crypto.subtle.digest(\"SHA-256\", new Uint8Array(source.body));\n const sha256 = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n if (sha256 !== source.sha256) {\n throw new ApplicationError(\"Attachment integrity check failed\", { code: \"ATTACHMENT_INTEGRITY_FAILED\" });\n }\n return this.store.recordAttachment(input, { ...input, sha256, bytes: source.body.byteLength });\n }\n}\n";
|
|
2
2
|
export declare const STARTER_REVIEW_JOB_TEST = "import { expect, test } from \"bun:test\";\nimport { VerifyReviewAttachment, type ReviewAttachmentResult } from \"../src/review/attachment\";\n\nconst input = { reviewId: \"review\", version: 2, artifactId: \"11111111-1111-4111-8111-111111111111\" };\nconst body = new TextEncoder().encode(\"review attachment\");\nconst sha256 = Array.from(new Uint8Array(await crypto.subtle.digest(\"SHA-256\", body)),\n byte => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\ntest(\"attachment job awaits the authorized reader and the committed result\", async () => {\n let recorded: ReviewAttachmentResult | undefined;\n const job = new VerifyReviewAttachment({\n async readResult() { return null; },\n async readAttachment() { return { body, sha256 }; },\n async recordAttachment(_input, result) { await Promise.resolve(); recorded = result; return result; },\n });\n expect(await job.run(input)).toEqual({ ...input, sha256, bytes: body.byteLength });\n expect(recorded).toEqual({ ...input, sha256, bytes: body.byteLength });\n});\n\ntest(\"missing authorization, corrupted bytes and persistence failures are not successful jobs\", async () => {\n let records = 0;\n for (const readAttachment of [\n async () => { throw new Error(\"Access denied\"); },\n async () => ({ body, sha256: \"0\".repeat(64) }),\n async () => ({ body: new Uint8Array(), sha256 }),\n async () => ({ body: new Uint8Array(1048577), sha256 }),\n ]) {\n const job = new VerifyReviewAttachment({\n async readResult() { return null; },\n readAttachment, async recordAttachment(_input, result) { records++; return result; },\n });\n await expect(job.run(input)).rejects.toThrow();\n }\n expect(records).toBe(0);\n await expect(new VerifyReviewAttachment({\n async readResult() { return null; },\n async readAttachment() { return { body, sha256 }; },\n async recordAttachment() { throw new Error(\"Commit failed\"); },\n }).run(input)).rejects.toThrow(\"Commit failed\");\n});\n\ntest(\"a reauthorized committed result avoids another download or write\", async () => {\n const result = { ...input, sha256, bytes: body.byteLength };\n const job = new VerifyReviewAttachment({\n async readResult() { return result; },\n async readAttachment() { throw new Error(\"Must not download again\"); },\n async recordAttachment() { throw new Error(\"Must not write again\"); },\n });\n expect(await job.run(input)).toEqual(result);\n await expect(new VerifyReviewAttachment({\n async readResult() { throw new Error(\"Access revoked\"); },\n async readAttachment() { throw new Error(\"Must not download\"); },\n async recordAttachment() { throw new Error(\"Must not write\"); },\n }).run(input)).rejects.toThrow(\"Access revoked\");\n});\n";
|
|
@@ -101,6 +101,11 @@ export interface AppToolArguments {
|
|
|
101
101
|
search?: string;
|
|
102
102
|
start?: string;
|
|
103
103
|
end?: string;
|
|
104
|
+
/** `app context`: bounded JSON envelope or generated execution JSON-lines file. */
|
|
105
|
+
events?: string;
|
|
106
|
+
/** `app context`: exact opaque request ID to correlate from events. */
|
|
107
|
+
request_id?: string;
|
|
108
|
+
"request-id"?: string;
|
|
104
109
|
}
|
|
105
110
|
export interface ToolResult {
|
|
106
111
|
isError?: boolean;
|
|
@@ -14,6 +14,7 @@ type FunctionLimits = {
|
|
|
14
14
|
};
|
|
15
15
|
export type FunctionConfigInput = {
|
|
16
16
|
verify_jwt?: boolean;
|
|
17
|
+
bytecode?: "off" | "auto" | "required";
|
|
17
18
|
background_routes?: string[];
|
|
18
19
|
framework?: FunctionFramework;
|
|
19
20
|
capabilities?: FunctionCapabilities;
|
|
@@ -25,6 +26,7 @@ export type FunctionIdentityResponse = {
|
|
|
25
26
|
active_version: string;
|
|
26
27
|
activation_id: string;
|
|
27
28
|
verify_jwt: boolean;
|
|
29
|
+
bytecode?: FunctionConfigInput["bytecode"];
|
|
28
30
|
background_routes: string[];
|
|
29
31
|
framework?: FunctionFramework;
|
|
30
32
|
version?: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function validateFunctionAot(args: Record<string, unknown>): void;
|
|
2
|
+
export declare function buildFunctionAot(entrypoint: string, outfile: string, options?: {
|
|
3
|
+
appEntry?: string;
|
|
4
|
+
minify?: boolean;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
code: string;
|
|
7
|
+
prebundled: true;
|
|
8
|
+
expectedSha256: string;
|
|
9
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/cli",
|
|
3
|
-
"version": "0.103.
|
|
3
|
+
"version": "0.103.5",
|
|
4
4
|
"description": "Project-scoped CLI for SupaCloud users",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,19 +52,19 @@
|
|
|
52
52
|
"directory": "packages/cli"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@supacloud/compiler": "^0.
|
|
56
|
-
"@supacloud/delivery": "0.19.
|
|
57
|
-
"@supacloud/db": "0.18.
|
|
55
|
+
"@supacloud/compiler": "^0.47.1",
|
|
56
|
+
"@supacloud/delivery": "0.19.2",
|
|
57
|
+
"@supacloud/db": "0.18.2",
|
|
58
58
|
"typebox": "1.3.37"
|
|
59
59
|
},
|
|
60
60
|
"overrides": {
|
|
61
|
-
"@supacloud/contracts": "0.11.
|
|
62
|
-
"@supacloud/delivery": "0.19.
|
|
61
|
+
"@supacloud/contracts": "0.11.3",
|
|
62
|
+
"@supacloud/delivery": "0.19.2",
|
|
63
63
|
"@graphql-tools/utils": "^12.0.4"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@supabase/supabase-js": "2.117.3",
|
|
67
|
-
"@supacloud/elysia": "0.33.
|
|
67
|
+
"@supacloud/elysia": "0.33.3",
|
|
68
68
|
"@types/bun": "^1.4.3",
|
|
69
69
|
"effect": "4.0.5",
|
|
70
70
|
"elysia": "2.0.0-beta.32",
|