@somewhere-tech/cli 0.37.8 → 0.37.10
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/commands/cron.js +9 -4
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/init.js +15 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/lib/init-feature-template.js +437 -11
- package/dist/lib/init-feature-template.js.map +1 -1
- package/dist/lib/init-features.js +7 -2
- package/dist/lib/init-features.js.map +1 -1
- package/dist/lib/skills-pack.generated.js +8 -8
- package/dist/lib/skills-pack.generated.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/runtime/DECLARED-DATA-VENDOR.json +10 -8
- package/runtime/declared-data.cjs +490 -35
|
@@ -35,7 +35,13 @@ var require_declared_data_contract = __commonJS({
|
|
|
35
35
|
"worker/containers/compile/declared-data-contract.cjs"(exports2, module2) {
|
|
36
36
|
"use strict";
|
|
37
37
|
var identifier = /^[a-z_][a-z0-9_]*$/;
|
|
38
|
-
var types = /* @__PURE__ */ new Set(["integer", "number", "text", "boolean", "timestamp", "json", "blob"]);
|
|
38
|
+
var types = /* @__PURE__ */ new Set(["integer", "bigint", "number", "text", "boolean", "timestamp", "json", "blob"]);
|
|
39
|
+
function canonicalBigintText(value) {
|
|
40
|
+
if (typeof value !== "string" || !/^(0|-?[1-9][0-9]*)$/.test(value)) return false;
|
|
41
|
+
const digits = value.startsWith("-") ? value.slice(1) : value;
|
|
42
|
+
const limit = value.startsWith("-") ? "9223372036854775808" : "9223372036854775807";
|
|
43
|
+
return digits.length < limit.length || digits.length === limit.length && digits <= limit;
|
|
44
|
+
}
|
|
39
45
|
function fail(detail) {
|
|
40
46
|
throw new Error(`Invalid declared data contract: ${detail}`);
|
|
41
47
|
}
|
|
@@ -120,7 +126,7 @@ var require_declared_data_contract = __commonJS({
|
|
|
120
126
|
for (const [field, value] of entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
121
127
|
const column = columns.find((column2) => column2.n === field);
|
|
122
128
|
if (!column || ["json", "blob"].includes(column.t) || !(value === null || typeof value === "string" && value.length <= 256 || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value))) fail(`${name} public read predicate`);
|
|
123
|
-
if (value === null ? column.nul !== 1 : column.t === "boolean" ? typeof value !== "boolean" :
|
|
129
|
+
if (value === null ? column.nul !== 1 : column.t === "boolean" ? typeof value !== "boolean" : column.t === "integer" ? !Number.isSafeInteger(value) : column.t === "number" ? typeof value !== "number" : column.t === "bigint" ? !canonicalBigintText(value) : ["text", "timestamp"].includes(column.t) ? typeof value !== "string" : true) fail(`${name} public read predicate type`);
|
|
124
130
|
where[field] = value;
|
|
125
131
|
}
|
|
126
132
|
publicRead = { where };
|
|
@@ -211,7 +217,7 @@ var require_declared_data_contract = __commonJS({
|
|
|
211
217
|
return JSON.stringify({ version: 1, tables });
|
|
212
218
|
}
|
|
213
219
|
function columnType(column) {
|
|
214
|
-
const type = { integer: "number
|
|
220
|
+
const type = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
|
|
215
221
|
return column.nul ? `${type} | null` : type;
|
|
216
222
|
}
|
|
217
223
|
function shape(columns, names, mode, required = /* @__PURE__ */ new Set()) {
|
|
@@ -693,8 +699,39 @@ interface SomewhereAuthMfaRequired {
|
|
|
693
699
|
}
|
|
694
700
|
type SomewhereAuthLoginResult = SomewhereAuthIssuedSession | SomewhereAuthMfaRequired;
|
|
695
701
|
interface SomewhereAuthOtpSession extends SomewhereAuthIssuedSession {
|
|
696
|
-
// The redirect_uri passed to signInWithOtp, or null.
|
|
702
|
+
// The redirect_uri passed to signInWithOtp, or null. For an invitation
|
|
703
|
+
// link: the invite's redirect_uri with ?invite_id=<id> added.
|
|
697
704
|
redirect_uri: string | null;
|
|
705
|
+
// Present when the verified token was an invitation.
|
|
706
|
+
invite_id?: string;
|
|
707
|
+
}
|
|
708
|
+
type SomewhereAuthInviteStatus = 'pending' | 'accepted' | 'revoked' | 'expired';
|
|
709
|
+
interface SomewhereAuthInvite {
|
|
710
|
+
id: string;
|
|
711
|
+
email: string;
|
|
712
|
+
status: SomewhereAuthInviteStatus;
|
|
713
|
+
redirect_uri: string;
|
|
714
|
+
// Server-side only; never in the email or the link.
|
|
715
|
+
data: SomewhereJsonObject | null;
|
|
716
|
+
expires_at: number;
|
|
717
|
+
created_at: number;
|
|
718
|
+
accepted_at: number | null;
|
|
719
|
+
accepted_user_id: string | null;
|
|
720
|
+
revoked_at: number | null;
|
|
721
|
+
}
|
|
722
|
+
interface SomewhereAuthInviteOptions {
|
|
723
|
+
email: string;
|
|
724
|
+
// A path in the app such as '/join', or an allowed absolute URL.
|
|
725
|
+
redirect_uri: string;
|
|
726
|
+
// At most 4096 bytes of JSON.
|
|
727
|
+
data?: SomewhereJsonObject;
|
|
728
|
+
// Seconds, 900 to 2592000; default 604800 (7 days).
|
|
729
|
+
expires_in?: number;
|
|
730
|
+
}
|
|
731
|
+
interface SomewhereAuthInviteSent {
|
|
732
|
+
invite: SomewhereAuthInvite;
|
|
733
|
+
delivery: 'sent' | 'pending';
|
|
734
|
+
message_id?: string;
|
|
698
735
|
}
|
|
699
736
|
interface SomewhereAuthOAuthSession {
|
|
700
737
|
user: SomewhereAuthSignedInUser;
|
|
@@ -800,6 +837,19 @@ interface SomewhereRuntimeAuthMfa {
|
|
|
800
837
|
verify(options: { token: string; enrollment_id: string; activation_token: string; code: string }):
|
|
801
838
|
Promise<{ enabled: true; backup_codes: string[] }>;
|
|
802
839
|
unenroll(options: { token: string; code: string }): Promise<{ enabled: false }>;
|
|
840
|
+
// The same steps for the request's own session (cookie or Bearer). verifyWithCookie ends every session and clears the cookies.
|
|
841
|
+
enrollWithCookie(req: Request): Promise<{
|
|
842
|
+
secret: string; enrollment_id: string; otpauth_uri: string; issuer: string; account: string;
|
|
843
|
+
}>;
|
|
844
|
+
reauthenticateWithCookie(req: Request, options: { enrollment_id: string; method: 'password'; password: string }):
|
|
845
|
+
Promise<{ method: 'password'; activation_token: string; expires_in_seconds: number }>;
|
|
846
|
+
reauthenticateWithCookie(req: Request, options: { enrollment_id: string; method: 'email' }):
|
|
847
|
+
Promise<{ method: 'email'; challenge_id: string; expires_in_seconds: number; pending?: true }>;
|
|
848
|
+
verifyReauthenticationWithCookie(req: Request, options: { enrollment_id: string; challenge_id: string; code: string }):
|
|
849
|
+
Promise<{ method: 'email'; activation_token: string; expires_in_seconds: number }>;
|
|
850
|
+
verifyWithCookie(req: Request, options: { enrollment_id: string; activation_token: string; code: string }):
|
|
851
|
+
Promise<{ enabled: true; backup_codes: string[] }>;
|
|
852
|
+
unenrollWithCookie(req: Request, options: { code: string }): Promise<{ enabled: false }>;
|
|
803
853
|
}
|
|
804
854
|
interface SomewhereRuntimeAuth {
|
|
805
855
|
signup(options: SomewhereAuthSignupOptions): Promise<SomewhereAuthIssuedSession>;
|
|
@@ -823,7 +873,17 @@ interface SomewhereRuntimeAuth {
|
|
|
823
873
|
// Email change is verify-gated: sends a code, never writes the address immediately.
|
|
824
874
|
updateProfile(token: string, options: SomewhereAuthEmailChangeRequest): Promise<SomewhereAuthEmailChangeStarted>;
|
|
825
875
|
updateProfileWithCookie(req: Request, options: SomewhereAuthProfileUpdate): Promise<{ user: SomewhereAuthUpdatedUser }>;
|
|
876
|
+
updateProfileWithCookie(req: Request, options: SomewhereAuthEmailChangeRequest): Promise<{ email_change: SomewhereAuthEmailChange }>;
|
|
877
|
+
// Email change steps for the request's own session: change_id is email_change.id, code the 6-digit emailed code.
|
|
878
|
+
verifyEmailChangeCurrentWithCookie(req: Request, options: { change_id: string; code: string }): Promise<SomewhereAuthVerificationSent>;
|
|
879
|
+
verifyEmailChangeWithCookie(req: Request, options: { change_id: string; code: string }): Promise<{ changed: true; email: string }>;
|
|
880
|
+
// Email verification for the request's own session (cookie or Bearer); no token in app code.
|
|
881
|
+
requestEmailVerificationWithCookie(req: Request): Promise<SomewhereAuthVerificationSent>;
|
|
882
|
+
verifyEmailWithCookie(req: Request, options: { code: string }): Promise<{ verified: true }>;
|
|
826
883
|
deleteUser(token: string): Promise<{ deleted: true }>;
|
|
884
|
+
// Password change and account deletion end every session of the account; these clear the cookies.
|
|
885
|
+
updatePasswordWithCookie(req: Request, options: { new_password: string; current_password?: string }): Promise<{ updated: true }>;
|
|
886
|
+
deleteUserWithCookie(req: Request): Promise<{ deleted: true }>;
|
|
827
887
|
googleUrl(options: { redirect_uri: string }): string;
|
|
828
888
|
githubUrl(options: { redirect_uri: string }): string;
|
|
829
889
|
discordUrl(options: { redirect_uri: string }): string;
|
|
@@ -841,6 +901,14 @@ interface SomewhereRuntimeAuth {
|
|
|
841
901
|
logoutWithCookie(req: Request): Promise<{ ok: true }>;
|
|
842
902
|
signInWithOtp(options: { email: string; redirect_uri?: string }): Promise<SomewhereAuthOtpSent>;
|
|
843
903
|
verifyOtp(options: { token: string }): Promise<SomewhereAuthOtpSession>;
|
|
904
|
+
// One invite email; the link signs the invitee in as the invited address.
|
|
905
|
+
invite(options: SomewhereAuthInviteOptions): Promise<SomewhereAuthInviteSent>;
|
|
906
|
+
listInvites(options?: { status?: SomewhereAuthInviteStatus; email?: string; limit?: number }): Promise<SomewhereAuthInvite[]>;
|
|
907
|
+
getInvite(id: string): Promise<SomewhereAuthInvite | null>;
|
|
908
|
+
revokeInvite(id: string): Promise<SomewhereAuthInvite>;
|
|
909
|
+
// The accepted invite, only for the signed-in account that accepted it;
|
|
910
|
+
// otherwise throws INVITE_NOT_ACCEPTED_BY_YOU (403) or AUTH_REQUIRED (401).
|
|
911
|
+
getAcceptedInvite(req: Request, id: string): Promise<SomewhereAuthInvite>;
|
|
844
912
|
anonSession(): Promise<SomewhereAuthAnonSession>;
|
|
845
913
|
readonly moderation: SomewhereRuntimeAuthModeration;
|
|
846
914
|
}
|
|
@@ -1214,6 +1282,8 @@ interface SomewhereCalendarPolicyInput {
|
|
|
1214
1282
|
advance_notice_hours?: number | null;
|
|
1215
1283
|
bookable_from?: SomewhereCalendarInstant | null;
|
|
1216
1284
|
turnaround_hours?: number | null;
|
|
1285
|
+
// Seats per exact [start, end) class slot. Omitted or null: one reservation per overlapping range.
|
|
1286
|
+
capacity?: number | null;
|
|
1217
1287
|
}
|
|
1218
1288
|
type SomewhereCalendarSetPolicyOptions = { resource: string } & ({ policy: SomewhereCalendarPolicyInput } | (SomewhereCalendarPolicyInput & { policy?: never }));
|
|
1219
1289
|
interface SomewhereCalendarPolicy {
|
|
@@ -1223,6 +1293,7 @@ interface SomewhereCalendarPolicy {
|
|
|
1223
1293
|
advance_notice_hours: number | null;
|
|
1224
1294
|
bookable_from_ms: number | null;
|
|
1225
1295
|
turnaround_hours: number | null;
|
|
1296
|
+
capacity: number | null;
|
|
1226
1297
|
created_at: number | null;
|
|
1227
1298
|
updated_at: number | null;
|
|
1228
1299
|
}
|
|
@@ -1238,6 +1309,10 @@ interface SomewhereCalendarAvailabilityResult {
|
|
|
1238
1309
|
expires_at: number | null;
|
|
1239
1310
|
}[];
|
|
1240
1311
|
free: { start_ms: number; end_ms: number }[];
|
|
1312
|
+
// null for an exclusive resource.
|
|
1313
|
+
capacity: number | null;
|
|
1314
|
+
// Existing class slots overlapping the range (full ones included); not a schedule.
|
|
1315
|
+
slots: { start_ms: number; end_ms: number; capacity: number; reserved: number; remaining: number }[];
|
|
1241
1316
|
}
|
|
1242
1317
|
interface SomewhereCalendarListExtras {
|
|
1243
1318
|
range?: SomewhereCalendarReadRange | null;
|
|
@@ -1687,13 +1762,9 @@ interface SomewhereAnalyticsTrackOptions {
|
|
|
1687
1762
|
referrer?: string | null;
|
|
1688
1763
|
user_agent?: string | null;
|
|
1689
1764
|
}
|
|
1690
|
-
|
|
1691
|
-
recorded: true;
|
|
1692
|
-
|
|
1693
|
-
// Derived from the signed-in request user; never caller-supplied.
|
|
1694
|
-
user_id: string | null;
|
|
1695
|
-
attribution: 'app_user' | 'project';
|
|
1696
|
-
}
|
|
1765
|
+
type SomewhereAnalyticsTrackResult =
|
|
1766
|
+
| { recorded: true; event: string; user_id: string | null; attribution: 'app_user' | 'project' }
|
|
1767
|
+
| { recorded: false; reason: 'consent_pending' | 'consent_declined' | 'consent_unavailable' };
|
|
1697
1768
|
interface SomewhereAnalyticsQueryOptions {
|
|
1698
1769
|
event?: string;
|
|
1699
1770
|
from?: string | number;
|
|
@@ -1727,6 +1798,20 @@ interface SomewhereRuntimeContext {
|
|
|
1727
1798
|
// ---- sw.ai ----
|
|
1728
1799
|
type SomewhereAiProvider = 'anthropic' | 'openai' | 'xai' | 'workers-ai' | 'deepseek' | 'deepinfra';
|
|
1729
1800
|
interface SomewhereAiInputBlock { type: string; [key: string]: unknown }
|
|
1801
|
+
/** Native Anthropic image input. Encode file bytes without a data: URL prefix. */
|
|
1802
|
+
interface SomewhereAiImageInputBlock extends SomewhereAiInputBlock {
|
|
1803
|
+
type: 'image';
|
|
1804
|
+
source: {
|
|
1805
|
+
type: 'base64';
|
|
1806
|
+
media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
1807
|
+
data: string;
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
/** Native Anthropic PDF input for one-shot extraction. */
|
|
1811
|
+
interface SomewhereAiDocumentInputBlock extends SomewhereAiInputBlock {
|
|
1812
|
+
type: 'document';
|
|
1813
|
+
source: { type: 'base64'; media_type: 'application/pdf'; data: string };
|
|
1814
|
+
}
|
|
1730
1815
|
interface SomewhereAiMessage {
|
|
1731
1816
|
role: 'user' | 'assistant' | 'system';
|
|
1732
1817
|
content: string | readonly SomewhereAiInputBlock[];
|
|
@@ -1794,6 +1879,7 @@ interface SomewhereAiChatResult {
|
|
|
1794
1879
|
interface __SomewhereAiChatFn {
|
|
1795
1880
|
(options: SomewhereAiChatOptions & { stream: true }): Promise<ReadableStream<Uint8Array>>;
|
|
1796
1881
|
(options: SomewhereAiChatOptions & { stream?: false }): Promise<SomewhereAiChatResult>;
|
|
1882
|
+
/** \`stream\` typed as plain boolean, e.g. \`const o = { messages, stream: false }\` (false widens to boolean): narrow the result with \`instanceof ReadableStream\`, or write \`stream: false as const\`. */
|
|
1797
1883
|
(options: SomewhereAiChatOptions): Promise<SomewhereAiChatResult | ReadableStream<Uint8Array>>;
|
|
1798
1884
|
}
|
|
1799
1885
|
interface SomewhereAiConversationSummary {
|
|
@@ -1878,7 +1964,13 @@ interface SomewhereAiUserMemory {
|
|
|
1878
1964
|
clear(): Promise<{ cleared: true }>;
|
|
1879
1965
|
compact(schema: SomewhereJsonObject, options?: SomewhereAiUserMemoryCompactOptions): Promise<SomewhereJsonObject>;
|
|
1880
1966
|
}
|
|
1881
|
-
type SomewhereAiTranscribeOptions = {
|
|
1967
|
+
type SomewhereAiTranscribeOptions = {
|
|
1968
|
+
model?: string;
|
|
1969
|
+
/** Spoken language hint for the default transcription model, e.g. 'en'. */
|
|
1970
|
+
language?: string;
|
|
1971
|
+
/** Names, vocabulary or context for the default transcription model. */
|
|
1972
|
+
prompt?: string;
|
|
1973
|
+
} & (
|
|
1882
1974
|
| { audio: string; audio_url?: string }
|
|
1883
1975
|
| { audio?: string; audio_url: string }
|
|
1884
1976
|
);
|
|
@@ -1915,9 +2007,16 @@ interface SomewhereAiGenerateImageOptions {
|
|
|
1915
2007
|
width?: number;
|
|
1916
2008
|
height?: number;
|
|
1917
2009
|
steps?: number;
|
|
2010
|
+
// gpt-image-2.5-flare only; refused on other models.
|
|
2011
|
+
quality?: 'low' | 'medium' | 'high';
|
|
2012
|
+
background?: 'auto' | 'opaque' | 'transparent';
|
|
1918
2013
|
storage?: string;
|
|
1919
2014
|
}
|
|
1920
|
-
interface SomewhereAiGenerateImageStored extends __SomewhereAiStoredFile {
|
|
2015
|
+
interface SomewhereAiGenerateImageStored extends __SomewhereAiStoredFile {
|
|
2016
|
+
width: number; height: number; steps: number;
|
|
2017
|
+
quality?: 'low' | 'medium' | 'high';
|
|
2018
|
+
background?: 'auto' | 'opaque' | 'transparent';
|
|
2019
|
+
}
|
|
1921
2020
|
interface SomewhereAiRemoveBackgroundOptions { image_url: string; model?: string; storage?: string }
|
|
1922
2021
|
type SomewhereAiEmbeddingsOptions = {
|
|
1923
2022
|
model?: string;
|
|
@@ -2089,6 +2188,9 @@ interface SomewhereAgentStepCheckpoint {
|
|
|
2089
2188
|
__sw_agent_turn: true;
|
|
2090
2189
|
state: SomewhereJsonObject;
|
|
2091
2190
|
done: boolean;
|
|
2191
|
+
// Present only when the run stopped at its budget and the platform's signed callback reads terminal budget
|
|
2192
|
+
// envelopes; the platform then records the run as failed with this code. Otherwise the budget stop throws.
|
|
2193
|
+
error?: { code: 'AI_SPEND_CAP_EXCEEDED' | 'AI_COST_PENDING'; message: string };
|
|
2092
2194
|
agent_id?: never;
|
|
2093
2195
|
status?: never;
|
|
2094
2196
|
max_steps?: never;
|
|
@@ -2485,17 +2587,91 @@ interface SomewhereRuntimeNotifications {
|
|
|
2485
2587
|
interface SomewherePushSendOptions {
|
|
2486
2588
|
// Required: a string or any JSON-serializable value.
|
|
2487
2589
|
payload: string | number | boolean | object;
|
|
2488
|
-
//
|
|
2590
|
+
// Exactly one target; a missing, null or empty one is refused.
|
|
2489
2591
|
user_id?: string;
|
|
2490
2592
|
userId?: string;
|
|
2491
2593
|
endpoint?: string;
|
|
2594
|
+
subscription_id?: string;
|
|
2595
|
+
subscriptionId?: string;
|
|
2596
|
+
// 'developer': devices registered with developer credentials. 'all': every device.
|
|
2597
|
+
audience?: 'developer' | 'all';
|
|
2492
2598
|
ttl?: number;
|
|
2493
2599
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2600
|
+
type SomewherePushPlatform = 'web' | 'test';
|
|
2601
|
+
type SomewherePushDeliveryOutcome = 'sent' | 'failed' | 'gone' | 'captured';
|
|
2602
|
+
interface SomewherePushDeviceResult {
|
|
2603
|
+
delivery_id: string;
|
|
2604
|
+
subscription_id: string;
|
|
2605
|
+
user_id: string | null;
|
|
2606
|
+
platform: SomewherePushPlatform;
|
|
2607
|
+
device_label: string | null;
|
|
2608
|
+
endpoint_host: string | null;
|
|
2609
|
+
outcome: SomewherePushDeliveryOutcome;
|
|
2610
|
+
http_status: number | null;
|
|
2611
|
+
error: string | null;
|
|
2612
|
+
}
|
|
2613
|
+
interface SomewherePushSendResult {
|
|
2614
|
+
sent: number; failed: number; gone: number; captured: number; recipients: number;
|
|
2615
|
+
// false: the notifications went out but their history was not recorded.
|
|
2616
|
+
history_recorded: boolean;
|
|
2617
|
+
devices: SomewherePushDeviceResult[];
|
|
2618
|
+
}
|
|
2619
|
+
// The object PushManager.subscribe() returns, or its toJSON().
|
|
2620
|
+
interface SomewherePushBrowserSubscription {
|
|
2621
|
+
endpoint: string;
|
|
2622
|
+
keys: { p256dh: string; auth: string };
|
|
2623
|
+
}
|
|
2624
|
+
interface SomewherePushSubscribeResult {
|
|
2625
|
+
id: string; platform: SomewherePushPlatform; owner: 'app_user' | 'developer';
|
|
2626
|
+
created: boolean; reassigned?: boolean;
|
|
2627
|
+
}
|
|
2628
|
+
interface SomewherePushDevice {
|
|
2629
|
+
// 'legacy': stored before owners were verified; no send reaches it until it re-registers.
|
|
2630
|
+
id: string; user_id: string | null; owner: 'app_user' | 'developer' | 'legacy';
|
|
2631
|
+
platform: SomewherePushPlatform; device_label: string | null; endpoint_host: string | null;
|
|
2632
|
+
created_at: string; updated_at: string | null;
|
|
2633
|
+
}
|
|
2634
|
+
interface SomewherePushDelivery {
|
|
2635
|
+
id: string; subscription_id: string; user_id: string | null; platform: SomewherePushPlatform;
|
|
2636
|
+
device_label: string | null; endpoint_host: string | null; outcome: SomewherePushDeliveryOutcome;
|
|
2637
|
+
http_status: number | null; error: string | null; title: string | null; url: string | null;
|
|
2638
|
+
// Present on test-inbox deliveries only.
|
|
2639
|
+
payload?: unknown;
|
|
2640
|
+
created_at: string;
|
|
2641
|
+
}
|
|
2642
|
+
interface SomewherePushHistoryOptions {
|
|
2643
|
+
user_id?: string;
|
|
2644
|
+
userId?: string;
|
|
2645
|
+
subscription_id?: string;
|
|
2646
|
+
subscriptionId?: string;
|
|
2647
|
+
limit?: number;
|
|
2648
|
+
}
|
|
2496
2649
|
interface SomewhereRuntimePush {
|
|
2497
2650
|
vapidPublicKey(): Promise<{ vapid_public_key: string }>;
|
|
2651
|
+
// Registers a device for the signed-in user of this request (AUTH_REQUIRED without one).
|
|
2652
|
+
subscribe(
|
|
2653
|
+
subscription: SomewherePushBrowserSubscription | { toJSON(): SomewherePushBrowserSubscription } | { test: true },
|
|
2654
|
+
options?: { label?: string },
|
|
2655
|
+
): Promise<SomewherePushSubscribeResult>;
|
|
2656
|
+
unsubscribe(target: string | { endpoint: string } | { test: true }): Promise<{ deleted: boolean }>;
|
|
2498
2657
|
send(options: SomewherePushSendOptions): Promise<SomewherePushSendResult>;
|
|
2658
|
+
subscriptions(options?: { user_id?: string; userId?: string; limit?: number }): Promise<{ subscriptions: SomewherePushDevice[] }>;
|
|
2659
|
+
deliveries(options?: SomewherePushHistoryOptions): Promise<{ deliveries: SomewherePushDelivery[] }>;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// The push browser helper (tsk_80c0ed92). The compiler maps this specifier to
|
|
2663
|
+
// the same-origin /__sw/push/client.js the project origin serves.
|
|
2664
|
+
declare module 'somewhere:push' {
|
|
2665
|
+
export interface PushDeviceResult {
|
|
2666
|
+
endpoint?: string; id: string; platform: 'web' | 'test'; owner: 'app_user' | 'developer';
|
|
2667
|
+
created: boolean; reassigned?: boolean;
|
|
2668
|
+
}
|
|
2669
|
+
export interface PushStatus { supported: boolean; permission: NotificationPermission | 'unsupported'; subscribed: boolean; endpoint: string | null }
|
|
2670
|
+
export interface PushMessage { type: 'push' | 'click'; payload: unknown }
|
|
2671
|
+
export function enablePush(options?: { test?: boolean; label?: string }): Promise<PushDeviceResult>;
|
|
2672
|
+
export function disablePush(options?: { test?: boolean }): Promise<{ deleted: boolean }>;
|
|
2673
|
+
export function pushStatus(): Promise<PushStatus>;
|
|
2674
|
+
export function listenToPush(handler: (message: PushMessage) => void): () => void;
|
|
2499
2675
|
}
|
|
2500
2676
|
|
|
2501
2677
|
// \u2500\u2500 sw.queue / sw.jobs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -2521,6 +2697,8 @@ interface SomewhereJobsCreateOptions {
|
|
|
2521
2697
|
priority?: 'normal' | 'low';
|
|
2522
2698
|
// Generated per call when omitted.
|
|
2523
2699
|
idempotency_key?: string;
|
|
2700
|
+
// Earliest start: an ISO-8601 time with an explicit offset, e.g. '2026-10-05T09:00:00Z'. Must be in the future.
|
|
2701
|
+
run_at?: string;
|
|
2524
2702
|
agent?: { messages: readonly unknown[]; max_steps?: number; max_turns?: number; deployment_version?: string };
|
|
2525
2703
|
}
|
|
2526
2704
|
interface SomewhereJobsRecovery {
|
|
@@ -2537,6 +2715,8 @@ interface SomewhereJobsCreateResult {
|
|
|
2537
2715
|
recovery: SomewhereJobsRecovery | null;
|
|
2538
2716
|
ownership_status?: 'app_user' | 'project_owner';
|
|
2539
2717
|
owner_subject_id?: string | null;
|
|
2718
|
+
// ISO time when run_at was given; status is 'scheduled' until then.
|
|
2719
|
+
run_at?: string | null;
|
|
2540
2720
|
}
|
|
2541
2721
|
interface SomewhereJob {
|
|
2542
2722
|
job_id: string;
|
|
@@ -2563,11 +2743,20 @@ interface SomewhereJob {
|
|
|
2563
2743
|
cron_id: string | null;
|
|
2564
2744
|
cron_scheduled_at: string | null;
|
|
2565
2745
|
trigger: 'scheduled' | 'manual' | null;
|
|
2746
|
+
run_at: string | null;
|
|
2566
2747
|
recovery: SomewhereJobsRecovery | null;
|
|
2567
2748
|
}
|
|
2749
|
+
interface SomewhereJobsCancelResult {
|
|
2750
|
+
job_id: string;
|
|
2751
|
+
status: 'cancelled';
|
|
2752
|
+
ownership_status: 'app_user' | 'project_owner';
|
|
2753
|
+
owner_subject_id: string | null;
|
|
2754
|
+
}
|
|
2568
2755
|
interface SomewhereRuntimeJobs {
|
|
2569
2756
|
create(options: SomewhereJobsCreateOptions): Promise<SomewhereJobsCreateResult>;
|
|
2570
2757
|
status(jobId: string): Promise<SomewhereJob>;
|
|
2758
|
+
// Cancels queued work. An ordinary handler already running finishes; its side effects remain.
|
|
2759
|
+
cancel(jobId: string): Promise<SomewhereJobsCancelResult>;
|
|
2571
2760
|
// True only for a platform-signed job/queue/cron delivery; never throws.
|
|
2572
2761
|
verifyInvocation(req: Request): Promise<boolean>;
|
|
2573
2762
|
}
|
|
@@ -2746,6 +2935,14 @@ interface __SomewhereTypedRequest<Input> extends Request { json(): Promise<Input
|
|
|
2746
2935
|
type ServerFunction<Contract extends { input: unknown; output: unknown }> =
|
|
2747
2936
|
(req: __SomewhereTypedRequest<Contract["input"]>, sw: SomewhereRuntimeContext) =>
|
|
2748
2937
|
Contract["output"] | Promise<Contract["output"]>;
|
|
2938
|
+
|
|
2939
|
+
// Outbound WebSocket client: fetch(url, { headers: { Upgrade: 'websocket' } })
|
|
2940
|
+
// returns the upgraded socket on the response (null on any other response),
|
|
2941
|
+
// and the function calls accept() before using it. The runtime fetch wrapper
|
|
2942
|
+
// and the pinned egress transport both hand that response through
|
|
2943
|
+
// (worker/src/runtime/fetch.ts, routes/code-egress.ts). Merged into lib.dom.
|
|
2944
|
+
interface Response { readonly webSocket: WebSocket | null }
|
|
2945
|
+
interface WebSocket { accept(): void }
|
|
2749
2946
|
`;
|
|
2750
2947
|
var ENDPOINT_DECLARATION2 = `
|
|
2751
2948
|
interface SomewhereEndpointUser {
|
|
@@ -2788,7 +2985,7 @@ interface SomewhereEndpointConfig<Auth extends SomewhereEndpointAuth, Schema ext
|
|
|
2788
2985
|
): unknown;
|
|
2789
2986
|
}
|
|
2790
2987
|
`;
|
|
2791
|
-
var DECLARED_COLUMN_TYPES = { integer: "number
|
|
2988
|
+
var DECLARED_COLUMN_TYPES = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "__SomewhereJson", blob: "number[]" };
|
|
2792
2989
|
function declaredOwnerColumn2(name, table, scopes) {
|
|
2793
2990
|
if (Object.prototype.hasOwnProperty.call(scopes, name)) return scopes[name];
|
|
2794
2991
|
const find = (policy) => !policy ? null : policy.k === "o" ? policy.c : policy.k === "a" ? find(policy.p[0]) || find(policy.p[1]) : null;
|
|
@@ -2848,7 +3045,7 @@ var require_typed_files = __commonJS({
|
|
|
2848
3045
|
const groupOption = member ? `${groupKey}: string | number; ` : "";
|
|
2849
3046
|
const ops = [];
|
|
2850
3047
|
if (collection.client.read) {
|
|
2851
|
-
ops.push(`list(options?: { ${groupOption.replace(": string | number; ", "?: string | number; ")}limit?: number; cursor?: string | null }): Promise<FileResult<{ items:
|
|
3048
|
+
ops.push(`list(options?: { ${groupOption.replace(": string | number; ", "?: string | number; ")}limit?: number; cursor?: string | null }): Promise<FileResult<{ items: Array<${meta}>; next: string | null; has_more: boolean }>>`);
|
|
2852
3049
|
ops.push(`get(id: string): Promise<FileResult<${meta}>>`);
|
|
2853
3050
|
ops.push("url(id: string): string");
|
|
2854
3051
|
ops.push("shareLink(id: string, options?: { expiresIn?: number }): Promise<FileResult<{ url: string; expires_at: string; expires_in: number }>>");
|
|
@@ -2998,8 +3195,8 @@ var require_schema_types = __commonJS({
|
|
|
2998
3195
|
references?: string; onDelete?: 'cascade' | 'restrict'; renamedFrom?: string;
|
|
2999
3196
|
}
|
|
3000
3197
|
interface ClientPermissions<Field extends string> {
|
|
3001
|
-
identity?: 'authenticated' | 'visitor'; read?: boolean | Field[]; publicRead?: boolean | { where: Partial<Record<Field, string | number | boolean | null>> };
|
|
3002
|
-
create?: Field[] | false | null; update?: Field[] | false | null; delete?: boolean;
|
|
3198
|
+
identity?: 'authenticated' | 'visitor'; read?: boolean | readonly Field[]; publicRead?: boolean | { where: Partial<Record<Field, string | number | boolean | null>> };
|
|
3199
|
+
create?: readonly Field[] | false | null; update?: readonly Field[] | false | null; delete?: boolean;
|
|
3003
3200
|
}
|
|
3004
3201
|
interface FileCollection { readonly __somewhereFileCollection: unique symbol }
|
|
3005
3202
|
type FileOperation = 'read' | 'upload' | 'replace' | 'delete';
|
|
@@ -3020,6 +3217,7 @@ function id(options?: { uuid?: boolean }): SomewhereSchemaDeclaration.Column;
|
|
|
3020
3217
|
function text(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
3021
3218
|
function number(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
|
|
3022
3219
|
function integer(options?: SomewhereSchemaDeclaration.ColumnOptions<number>): SomewhereSchemaDeclaration.Column;
|
|
3220
|
+
function bigint(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
3023
3221
|
function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): SomewhereSchemaDeclaration.Column;
|
|
3024
3222
|
function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
3025
3223
|
function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
|
|
@@ -3062,6 +3260,15 @@ module.exports = __toCommonJS(declared_data_vendor_entry_exports);
|
|
|
3062
3260
|
// worker/src/utils/db-schema-deploy/schema-ownership.ts
|
|
3063
3261
|
var SHARED_AUTHOR_COLUMN = "_sw_author_id";
|
|
3064
3262
|
|
|
3263
|
+
// worker/src/utils/db-schema-deploy/bigint-text.ts
|
|
3264
|
+
function isCanonicalBigintText(value) {
|
|
3265
|
+
if (!/^(0|-?[1-9][0-9]*)$/.test(value)) return false;
|
|
3266
|
+
const negative = value.startsWith("-");
|
|
3267
|
+
const digits = negative ? value.slice(1) : value;
|
|
3268
|
+
const limit = negative ? "9223372036854775808" : "9223372036854775807";
|
|
3269
|
+
return digits.length < limit.length || digits.length === limit.length && digits <= limit;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3065
3272
|
// worker/src/utils/db-schema-deploy/client-permissions.ts
|
|
3066
3273
|
function parseClientPermissions(value, columns, scope) {
|
|
3067
3274
|
const fail = (message) => ({ ok: false, message });
|
|
@@ -3105,7 +3312,7 @@ function parseClientPermissions(value, columns, scope) {
|
|
|
3105
3312
|
if (!(literal === null || typeof literal === "boolean" || typeof literal === "string" || typeof literal === "number" && Number.isFinite(literal))) return fail(`client.publicRead.where field "${rawName}" must equal a literal scalar.`);
|
|
3106
3313
|
if (typeof literal === "string" && literal.length > 256) return fail(`client.publicRead.where field "${rawName}" exceeds 256 characters.`);
|
|
3107
3314
|
if (literal === null && !column.nullable) return fail(`client.publicRead.where field "${rawName}" cannot equal null because it is required.`);
|
|
3108
|
-
if (literal !== null && (column.helper === "boolean" && typeof literal !== "boolean" || ["integer", "number"].includes(column.helper) && typeof literal !== "number" || ["text", "timestamp"].includes(column.helper) && typeof literal !== "string")) {
|
|
3315
|
+
if (literal !== null && (column.helper === "boolean" && typeof literal !== "boolean" || ["integer", "number"].includes(column.helper) && typeof literal !== "number" || column.helper === "integer" && !Number.isSafeInteger(literal) || column.helper === "bigint" && (typeof literal !== "string" || !isCanonicalBigintText(literal)) || ["text", "timestamp"].includes(column.helper) && typeof literal !== "string")) {
|
|
3109
3316
|
return fail(`client.publicRead.where field "${rawName}" must match its declared type.`);
|
|
3110
3317
|
}
|
|
3111
3318
|
normalized[name] = literal;
|
|
@@ -3204,7 +3411,7 @@ function bakedTableSchemaFromDeclared(declaredJson) {
|
|
|
3204
3411
|
if (col.helper === "id") {
|
|
3205
3412
|
t = col.uuid === true ? "text" : "integer";
|
|
3206
3413
|
implicitDefault = true;
|
|
3207
|
-
} else if (col.helper === "integer" || col.helper === "number" || col.helper === "text" || col.helper === "boolean" || col.helper === "timestamp" || col.helper === "json" || col.helper === "blob") {
|
|
3414
|
+
} else if (col.helper === "integer" || col.helper === "bigint" || col.helper === "number" || col.helper === "text" || col.helper === "boolean" || col.helper === "timestamp" || col.helper === "json" || col.helper === "blob") {
|
|
3208
3415
|
t = col.helper;
|
|
3209
3416
|
} else {
|
|
3210
3417
|
return null;
|
|
@@ -3347,6 +3554,229 @@ function bakedMemberFromDeclared(scope) {
|
|
|
3347
3554
|
return { g, m: s.membership.toLowerCase(), u: s.memberUser.toLowerCase(), mg, ...o !== void 0 ? { o } : {} };
|
|
3348
3555
|
}
|
|
3349
3556
|
|
|
3557
|
+
// worker/src/utils/db-schema-deploy/schema-constants.ts
|
|
3558
|
+
var LITERAL_WORDS = /* @__PURE__ */ new Set(["true", "false", "null"]);
|
|
3559
|
+
var STATEMENT_WORDS = /* @__PURE__ */ new Set([
|
|
3560
|
+
"import",
|
|
3561
|
+
"export",
|
|
3562
|
+
"default",
|
|
3563
|
+
"from",
|
|
3564
|
+
"const",
|
|
3565
|
+
"let",
|
|
3566
|
+
"var",
|
|
3567
|
+
"as",
|
|
3568
|
+
"function",
|
|
3569
|
+
"class"
|
|
3570
|
+
]);
|
|
3571
|
+
var isPunct = (t, value) => !!t && t.kind === "punct" && t.value === value;
|
|
3572
|
+
var OPENERS = "([{";
|
|
3573
|
+
var CLOSERS = ")]}";
|
|
3574
|
+
function append(into, tokens) {
|
|
3575
|
+
for (const t of tokens) into.push(t);
|
|
3576
|
+
}
|
|
3577
|
+
var isIdent = (t, value) => !!t && t.kind === "ident" && t.value === value;
|
|
3578
|
+
function resolveSchemaConstants(tokens, callable, maxTokens, fail) {
|
|
3579
|
+
const out = [];
|
|
3580
|
+
let i = 0;
|
|
3581
|
+
const imported = /* @__PURE__ */ new Set();
|
|
3582
|
+
while (isIdent(tokens[i], "import")) {
|
|
3583
|
+
const start = i;
|
|
3584
|
+
while (tokens[i].kind !== "eof" && !isIdent(tokens[i], "from")) {
|
|
3585
|
+
if (tokens[i].kind === "ident" && i > start) imported.add(tokens[i].value);
|
|
3586
|
+
i++;
|
|
3587
|
+
}
|
|
3588
|
+
if (isIdent(tokens[i], "from")) i += 2;
|
|
3589
|
+
if (isPunct(tokens[i], ";")) i++;
|
|
3590
|
+
append(out, tokens.slice(start, i));
|
|
3591
|
+
}
|
|
3592
|
+
const constants = /* @__PURE__ */ new Map();
|
|
3593
|
+
const declaredLater = /* @__PURE__ */ new Map();
|
|
3594
|
+
for (let k = i; k < tokens.length; k++) {
|
|
3595
|
+
const t = tokens[k];
|
|
3596
|
+
if ((isIdent(t, "const") || isIdent(t, "let") || isIdent(t, "var")) && tokens[k + 1]?.kind === "ident") {
|
|
3597
|
+
if (!declaredLater.has(tokens[k + 1].value)) declaredLater.set(tokens[k + 1].value, tokens[k + 1].line);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
let expanded = out.length;
|
|
3601
|
+
const budget = (line, adding) => {
|
|
3602
|
+
expanded += adding;
|
|
3603
|
+
if (expanded > maxTokens) {
|
|
3604
|
+
fail(line, "reusing constants here expands the declaration past the size db/schema.ts allows. Reuse fewer copies, or write the shared part once.");
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
const expandRange = (from, to, into, owner) => {
|
|
3608
|
+
const open2 = [];
|
|
3609
|
+
for (let k = from; k < to; k++) {
|
|
3610
|
+
const t = tokens[k];
|
|
3611
|
+
if (t.kind === "punct" && OPENERS.includes(t.value)) open2.push(t.value);
|
|
3612
|
+
else if (t.kind === "punct" && CLOSERS.includes(t.value)) open2.pop();
|
|
3613
|
+
if (isPunct(t, ".") && isPunct(tokens[k + 1], ".") && isPunct(tokens[k + 2], ".")) {
|
|
3614
|
+
const target = tokens[k + 3];
|
|
3615
|
+
const opener = open2.length > 0 ? open2[open2.length - 1] : null;
|
|
3616
|
+
if (!target || target.kind !== "ident") {
|
|
3617
|
+
fail(t.line, "a spread (...) must name a constant declared above, as in ...sharedColumns.");
|
|
3618
|
+
}
|
|
3619
|
+
const constant = lookup(target, owner);
|
|
3620
|
+
if (opener !== "{") {
|
|
3621
|
+
fail(t.line, `...${target.value} is only supported inside an object literal { \u2026 }. Reference ${target.value} directly, or write the values inline.`);
|
|
3622
|
+
}
|
|
3623
|
+
if (!constant.objectLiteral) {
|
|
3624
|
+
fail(t.line, `...${target.value} spreads a constant that is not an object literal { \u2026 }. Only object constants can be spread.`);
|
|
3625
|
+
}
|
|
3626
|
+
const inner = constant.tokens.slice(1, -1);
|
|
3627
|
+
if (inner.length > 0 && isPunct(inner[inner.length - 1], ",")) inner.pop();
|
|
3628
|
+
budget(t.line, Math.max(inner.length, 4));
|
|
3629
|
+
append(into, inner);
|
|
3630
|
+
if (inner.length === 0 && isPunct(tokens[k + 4], ",")) k++;
|
|
3631
|
+
k += 3;
|
|
3632
|
+
continue;
|
|
3633
|
+
}
|
|
3634
|
+
if (t.kind !== "ident") {
|
|
3635
|
+
into.push(t);
|
|
3636
|
+
continue;
|
|
3637
|
+
}
|
|
3638
|
+
const next = tokens[k + 1];
|
|
3639
|
+
const prev = tokens[k - 1];
|
|
3640
|
+
const keyPosition = isPunct(next, ":") && (isPunct(prev, "{") || isPunct(prev, ","));
|
|
3641
|
+
if (keyPosition || LITERAL_WORDS.has(t.value) || isPunct(prev, ".")) {
|
|
3642
|
+
into.push(t);
|
|
3643
|
+
continue;
|
|
3644
|
+
}
|
|
3645
|
+
if (owner === null && STATEMENT_WORDS.has(t.value)) {
|
|
3646
|
+
if (isIdent(t, "export") && isIdent(next, "const")) {
|
|
3647
|
+
fail(t.line, '"export const" is not supported in db/schema.ts; the file exports only the schema. Remove "export" from the constant.');
|
|
3648
|
+
}
|
|
3649
|
+
into.push(t);
|
|
3650
|
+
continue;
|
|
3651
|
+
}
|
|
3652
|
+
if (constants.has(t.value) || declaredLater.has(t.value)) {
|
|
3653
|
+
const constant = lookup(t, owner);
|
|
3654
|
+
if (isPunct(next, "(")) {
|
|
3655
|
+
fail(t.line, `${t.value} is a constant, not a function \u2014 write ${t.value} without "( \u2026 )".`);
|
|
3656
|
+
}
|
|
3657
|
+
if (isPunct(next, ".")) {
|
|
3658
|
+
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a property of a constant, which db/schema.ts cannot evaluate. Declare that part as its own constant, or write it inline.`);
|
|
3659
|
+
}
|
|
3660
|
+
budget(t.line, constant.tokens.length);
|
|
3661
|
+
append(into, constant.tokens);
|
|
3662
|
+
continue;
|
|
3663
|
+
}
|
|
3664
|
+
if (callable.has(t.value)) {
|
|
3665
|
+
into.push(t);
|
|
3666
|
+
continue;
|
|
3667
|
+
}
|
|
3668
|
+
if ((isPunct(next, "(") || isPunct(next, ".")) && owner === null) {
|
|
3669
|
+
into.push(t);
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
if (isPunct(next, ".")) {
|
|
3673
|
+
fail(t.line, `${t.value}.${tokens[k + 2]?.value ?? ""} reads a value at run time; db/schema.ts is read without running it. Write the literal value instead.`);
|
|
3674
|
+
}
|
|
3675
|
+
if (isPunct(next, "(")) {
|
|
3676
|
+
fail(t.line, `${t.value}(\u2026) is a function call; db/schema.ts is read without running it, so only the schema helpers can be called. Write the value inline, or declare it with const using schema helpers and literals.`);
|
|
3677
|
+
}
|
|
3678
|
+
fail(t.line, `"${t.value}" is not defined in db/schema.ts. Declare it above export default as const ${t.value} = \u2026; or write the value inline.`);
|
|
3679
|
+
}
|
|
3680
|
+
};
|
|
3681
|
+
const lookup = (t, owner) => {
|
|
3682
|
+
const constant = constants.get(t.value);
|
|
3683
|
+
if (constant) return constant;
|
|
3684
|
+
const at = declaredLater.get(t.value);
|
|
3685
|
+
if (owner === t.value) {
|
|
3686
|
+
fail(t.line, `constant ${t.value} refers to itself. A constant must be built from values declared above it.`);
|
|
3687
|
+
}
|
|
3688
|
+
if (at !== void 0) {
|
|
3689
|
+
fail(t.line, owner ? `constant ${owner} uses ${t.value}, which is declared later (line ${at}). Move ${t.value} above ${owner}; constants that refer to each other in a cycle cannot be resolved.` : `${t.value} is used before its declaration on line ${at}. Move the const above export default.`);
|
|
3690
|
+
}
|
|
3691
|
+
return fail(t.line, `"${t.value}" is not defined in db/schema.ts. Declare it above export default as const ${t.value} = \u2026; or write the value inline.`);
|
|
3692
|
+
};
|
|
3693
|
+
while (!isIdent(tokens[i], "export")) {
|
|
3694
|
+
const t = tokens[i];
|
|
3695
|
+
if (t.kind === "eof") return [...out, t];
|
|
3696
|
+
if (isIdent(t, "let") || isIdent(t, "var")) {
|
|
3697
|
+
fail(t.line, `"${t.value}" declares a changeable binding; db/schema.ts allows only const. Change "${t.value}" to "const".`);
|
|
3698
|
+
}
|
|
3699
|
+
if (!isIdent(t, "const")) {
|
|
3700
|
+
if (t.kind === "ident" && constants.has(t.value)) {
|
|
3701
|
+
fail(t.line, `${t.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated. Declare the final value once with const.`);
|
|
3702
|
+
}
|
|
3703
|
+
if (isIdent(t, "import")) fail(t.line, "imports must come first in db/schema.ts, before any const.");
|
|
3704
|
+
return [...out, ...tokens.slice(i)];
|
|
3705
|
+
}
|
|
3706
|
+
const nameTok = tokens[i + 1];
|
|
3707
|
+
if (!nameTok || nameTok.kind !== "ident") {
|
|
3708
|
+
fail(t.line, "destructuring (const { \u2026 } = / const [ \u2026 ] =) is not supported in db/schema.ts. Declare one constant per name: const name = \u2026;");
|
|
3709
|
+
}
|
|
3710
|
+
const name = nameTok.value;
|
|
3711
|
+
if (callable.has(name) || LITERAL_WORDS.has(name) || STATEMENT_WORDS.has(name) || name === "schema") {
|
|
3712
|
+
fail(nameTok.line, `const ${name} shadows a db/schema.ts helper or keyword. Choose another name, such as ${name}Rule.`);
|
|
3713
|
+
}
|
|
3714
|
+
if (imported.has(name)) {
|
|
3715
|
+
fail(nameTok.line, `const ${name} has the same name as an import. Choose another name.`);
|
|
3716
|
+
}
|
|
3717
|
+
if (constants.has(name)) fail(nameTok.line, `const ${name} is declared twice. Keep one declaration.`);
|
|
3718
|
+
if (isPunct(tokens[i + 2], ":")) {
|
|
3719
|
+
fail(tokens[i + 2].line, `const ${name} has a type annotation; db/schema.ts constants take their type from the helpers. Remove ": \u2026".`);
|
|
3720
|
+
}
|
|
3721
|
+
if (!isPunct(tokens[i + 2], "=")) fail(nameTok.line, `expected "=" after const ${name}.`);
|
|
3722
|
+
const start = i + 3;
|
|
3723
|
+
let k = start;
|
|
3724
|
+
let depth = 0;
|
|
3725
|
+
for (; tokens[k].kind !== "eof"; k++) {
|
|
3726
|
+
const v = tokens[k];
|
|
3727
|
+
if (v.kind === "punct" && OPENERS.includes(v.value)) depth++;
|
|
3728
|
+
else if (v.kind === "punct" && CLOSERS.includes(v.value)) {
|
|
3729
|
+
depth--;
|
|
3730
|
+
if (depth < 0) fail(v.line, `const ${name} has an unmatched "${v.value}". Balance its brackets.`);
|
|
3731
|
+
} else if (depth === 0 && (isPunct(v, ";") || isPunct(v, ",") || isIdent(v, "const") && !isIdent(tokens[k - 1], "as") || isIdent(v, "let") || isIdent(v, "var") || isIdent(v, "export"))) break;
|
|
3732
|
+
if (depth === 0 && isPunct(v, "=")) fail(v.line, `${name} is assigned twice; give each constant one value.`);
|
|
3733
|
+
}
|
|
3734
|
+
if (depth !== 0) fail(nameTok.line, `const ${name} has an unclosed bracket. Balance its brackets.`);
|
|
3735
|
+
let end2 = k;
|
|
3736
|
+
if (end2 - start >= 2 && isIdent(tokens[end2 - 2], "as") && isIdent(tokens[end2 - 1], "const")) end2 -= 2;
|
|
3737
|
+
if (end2 === start) fail(nameTok.line, `const ${name} has no value.`);
|
|
3738
|
+
if (isPunct(tokens[k], ",")) {
|
|
3739
|
+
fail(tokens[k].line, `one declaration declares several constants; write one const per name (const ${name} = \u2026;).`);
|
|
3740
|
+
}
|
|
3741
|
+
const value = [];
|
|
3742
|
+
expandRange(start, end2, value, name);
|
|
3743
|
+
const objectLiteral = isPunct(tokens[start], "{") && matchingClose(tokens, start) === end2 - 1;
|
|
3744
|
+
constants.set(name, { name, line: nameTok.line, tokens: value, objectLiteral });
|
|
3745
|
+
i = isPunct(tokens[k], ";") ? k + 1 : k;
|
|
3746
|
+
}
|
|
3747
|
+
if (isIdent(tokens[i], "export") && isIdent(tokens[i + 1], "const")) {
|
|
3748
|
+
fail(tokens[i].line, '"export const" is not supported in db/schema.ts; the file exports only the schema. Remove "export" from the constant.');
|
|
3749
|
+
}
|
|
3750
|
+
const body = [];
|
|
3751
|
+
const open = tokens.findIndex((v, k) => k > i && isPunct(v, "(") && isIdent(tokens[k - 1], "schema"));
|
|
3752
|
+
const close = open < 0 ? -1 : matchingClose(tokens, open);
|
|
3753
|
+
const end = close < 0 ? tokens.length : close + 1;
|
|
3754
|
+
expandRange(i, end, body, null);
|
|
3755
|
+
for (let k = end; k < tokens.length; k++) {
|
|
3756
|
+
const v = tokens[k];
|
|
3757
|
+
if (v.kind === "ident" && constants.has(v.value) && (isPunct(tokens[k + 1], "=") || isPunct(tokens[k + 1], "."))) {
|
|
3758
|
+
fail(v.line, `${v.value} is changed after its declaration; constants in db/schema.ts cannot be reassigned or mutated. Declare the final value once with const.`);
|
|
3759
|
+
}
|
|
3760
|
+
body.push(v);
|
|
3761
|
+
}
|
|
3762
|
+
if (out.length + body.length > maxTokens) {
|
|
3763
|
+
fail(tokens[i].line, "reusing constants here expands the declaration past the size db/schema.ts allows. Reuse fewer copies, or write the shared part once.");
|
|
3764
|
+
}
|
|
3765
|
+
return [...out, ...body];
|
|
3766
|
+
}
|
|
3767
|
+
function matchingClose(tokens, open) {
|
|
3768
|
+
let depth = 0;
|
|
3769
|
+
for (let k = open; k < tokens.length; k++) {
|
|
3770
|
+
const v = tokens[k];
|
|
3771
|
+
if (v.kind === "punct" && "([{".includes(v.value)) depth++;
|
|
3772
|
+
else if (v.kind === "punct" && ")]}".includes(v.value)) {
|
|
3773
|
+
depth--;
|
|
3774
|
+
if (depth === 0) return k;
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
return -1;
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3350
3780
|
// worker/src/utils/db-schema-deploy/extract-schema-relations.ts
|
|
3351
3781
|
function validateDeclaredRelations(tables, tableByName, knownColumns, markedGone, errors) {
|
|
3352
3782
|
const columnByName = (t, col) => t.columns.find((c) => c.name === col);
|
|
@@ -3713,6 +4143,7 @@ var COLUMN_HELPERS = /* @__PURE__ */ new Set([
|
|
|
3713
4143
|
"text",
|
|
3714
4144
|
"number",
|
|
3715
4145
|
"integer",
|
|
4146
|
+
"bigint",
|
|
3716
4147
|
"boolean",
|
|
3717
4148
|
"timestamp",
|
|
3718
4149
|
"json",
|
|
@@ -3720,6 +4151,17 @@ var COLUMN_HELPERS = /* @__PURE__ */ new Set([
|
|
|
3720
4151
|
]);
|
|
3721
4152
|
var SCOPE_HELPERS = /* @__PURE__ */ new Set(["owner", "shared", "serverOnly", "member", "anyOf", "parent"]);
|
|
3722
4153
|
var TABLE_MARKER_HELPERS = /* @__PURE__ */ new Set(["removedTable", "exported"]);
|
|
4154
|
+
var SCHEMA_CALLABLE = /* @__PURE__ */ new Set([
|
|
4155
|
+
...COLUMN_HELPERS,
|
|
4156
|
+
...SCOPE_HELPERS,
|
|
4157
|
+
...TABLE_MARKER_HELPERS,
|
|
4158
|
+
"schema",
|
|
4159
|
+
"table",
|
|
4160
|
+
"removed",
|
|
4161
|
+
"hasMany",
|
|
4162
|
+
"belongsTo",
|
|
4163
|
+
"files"
|
|
4164
|
+
]);
|
|
3723
4165
|
var SAFE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3724
4166
|
var RESERVED_PREFIXES = ["app_", "sqlite_", "d1_", "__sw_"];
|
|
3725
4167
|
var MAX_TABLES = 100;
|
|
@@ -3816,7 +4258,7 @@ function tokenize(source) {
|
|
|
3816
4258
|
i = j;
|
|
3817
4259
|
continue;
|
|
3818
4260
|
}
|
|
3819
|
-
if ("(){}[]
|
|
4261
|
+
if ("(){}[]:,;.=".includes(ch)) {
|
|
3820
4262
|
tokens.push({ kind: "punct", value: ch, line });
|
|
3821
4263
|
i++;
|
|
3822
4264
|
continue;
|
|
@@ -3983,7 +4425,7 @@ function readColumn(r, tableName, rawName, line) {
|
|
|
3983
4425
|
}
|
|
3984
4426
|
if (!COLUMN_HELPERS.has(helper)) {
|
|
3985
4427
|
throw new SchemaTsError(
|
|
3986
|
-
`line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Use one of: id, text, number, integer, boolean, timestamp, json, blob.`
|
|
4428
|
+
`line ${helperTok.line}: unknown column type "${helper}" on "${tableName}"."${rawName}". Use one of: id, text, number, integer, bigint, boolean, timestamp, json, blob.`
|
|
3987
4429
|
);
|
|
3988
4430
|
}
|
|
3989
4431
|
r.expectPunct("(", `after "${helper}"`);
|
|
@@ -4033,14 +4475,17 @@ function readColumn(r, tableName, rawName, line) {
|
|
|
4033
4475
|
col.autoNow = true;
|
|
4034
4476
|
break;
|
|
4035
4477
|
}
|
|
4036
|
-
const okType = value === null || t === "boolean" && typeof value === "boolean" || (t === "number" || t === "integer") && typeof value === "number" || (t === "text" || t === "timestamp" || t === "json") && typeof value === "string";
|
|
4478
|
+
const okType = value === null || t === "boolean" && typeof value === "boolean" || (t === "number" || t === "integer") && typeof value === "number" || (t === "text" || t === "timestamp" || t === "json" || t === "bigint") && typeof value === "string";
|
|
4037
4479
|
if (!okType) {
|
|
4038
4480
|
throw new SchemaTsError(
|
|
4039
|
-
`line ${optLine}: the default for "${tableName}"."${rawName}" (${t}) must be ${t === "boolean" ? "true or false" : t === "number" || t === "integer" ? "a number" : t === "blob" ? "omitted \u2014 blob columns cannot declare a default" : "a string"} or null.`
|
|
4481
|
+
`line ${optLine}: the default for "${tableName}"."${rawName}" (${t}) must be ${t === "boolean" ? "true or false" : t === "number" || t === "integer" ? "a number" : t === "bigint" ? "a whole number written as a string, like '9007199254740993'" : t === "blob" ? "omitted \u2014 blob columns cannot declare a default" : "a string"} or null.`
|
|
4040
4482
|
);
|
|
4041
4483
|
}
|
|
4042
|
-
if (t === "integer" && typeof value === "number" && !Number.
|
|
4043
|
-
throw new SchemaTsError(`line ${optLine}: the default for integer column "${tableName}"."${rawName}" must be a whole number.`);
|
|
4484
|
+
if (t === "integer" && typeof value === "number" && !Number.isSafeInteger(value)) {
|
|
4485
|
+
throw new SchemaTsError(Number.isInteger(value) ? `line ${optLine}: the default for integer column "${tableName}"."${rawName}" is outside JavaScript's exact whole-number range (\xB19007199254740991). Declare the column bigint() and write the default as a string.` : `line ${optLine}: the default for integer column "${tableName}"."${rawName}" must be a whole number.`);
|
|
4486
|
+
}
|
|
4487
|
+
if (t === "bigint" && typeof value === "string" && !isCanonicalBigintText(value)) {
|
|
4488
|
+
throw new SchemaTsError(`line ${optLine}: the default for bigint column "${tableName}"."${rawName}" must be a whole number between -9223372036854775808 and 9223372036854775807, written as a string with no sign other than a leading minus and no leading zeros.`);
|
|
4044
4489
|
}
|
|
4045
4490
|
col.hasDefault = true;
|
|
4046
4491
|
col.default = value;
|
|
@@ -4542,11 +4987,6 @@ function readTable(r, rawName, nameLine) {
|
|
|
4542
4987
|
}
|
|
4543
4988
|
const uniques = [];
|
|
4544
4989
|
for (const entry of uniqueEntries ?? []) {
|
|
4545
|
-
if (entry.columns.length < 2) {
|
|
4546
|
-
throw new SchemaTsError(
|
|
4547
|
-
`line ${entry.line}: the "unique" option on table "${rawName}" is for MULTI-column uniqueness. For a single column, put { unique: true } on the column itself.`
|
|
4548
|
-
);
|
|
4549
|
-
}
|
|
4550
4990
|
for (const colName of entry.columns) {
|
|
4551
4991
|
if (!known.has(colName)) {
|
|
4552
4992
|
throw new SchemaTsError(
|
|
@@ -4554,6 +4994,19 @@ function readTable(r, rawName, nameLine) {
|
|
|
4554
4994
|
);
|
|
4555
4995
|
}
|
|
4556
4996
|
}
|
|
4997
|
+
const single = entry.columns.length === 1 ? columns.find((c) => c.name === entry.columns[0]) : void 0;
|
|
4998
|
+
if (single) {
|
|
4999
|
+
if (single.helper === "id") {
|
|
5000
|
+
throw new SchemaTsError(`line ${entry.line}: the primary key "${single.name}" on table "${rawName}" is already unique; remove its one-column "unique" group.`);
|
|
5001
|
+
}
|
|
5002
|
+
single.unique = true;
|
|
5003
|
+
continue;
|
|
5004
|
+
}
|
|
5005
|
+
if (entry.columns.length < 2) {
|
|
5006
|
+
throw new SchemaTsError(
|
|
5007
|
+
`line ${entry.line}: the "unique" option on table "${rawName}" lists only "${entry.columns[0]}", the owner column the platform manages, and an owner column cannot be unique on its own. Pair it with another column (for example [['${entry.columns[0]}', 'kind']]) for per-owner uniqueness.`
|
|
5008
|
+
);
|
|
5009
|
+
}
|
|
4557
5010
|
uniques.push(entry.columns);
|
|
4558
5011
|
}
|
|
4559
5012
|
let client;
|
|
@@ -4584,12 +5037,14 @@ function extractSchemaTs(source) {
|
|
|
4584
5037
|
return { ok: false, errors: [`db/schema.ts is larger than ${Math.floor(MAX_SOURCE_BYTES / 1024)}KB \u2014 that is not a schema declaration.`] };
|
|
4585
5038
|
}
|
|
4586
5039
|
try {
|
|
4587
|
-
const r = new Reader(tokenize(source))
|
|
5040
|
+
const r = new Reader(resolveSchemaConstants(tokenize(source), SCHEMA_CALLABLE, MAX_SOURCE_BYTES, (line, message) => {
|
|
5041
|
+
throw new SchemaTsError(`line ${line}: ${message}`);
|
|
5042
|
+
}));
|
|
4588
5043
|
while (r.peek().kind === "ident" && r.peek().value === "import") skipImport(r);
|
|
4589
5044
|
const exp = r.expectIdent("to start the declaration");
|
|
4590
5045
|
if (exp.value !== "export") {
|
|
4591
5046
|
throw new SchemaTsError(
|
|
4592
|
-
`line ${exp.line}: db/schema.ts must contain only the schema declaration: optional imports, then "export default schema({ \u2026 })". Found "${exp.value}".`
|
|
5047
|
+
`line ${exp.line}: db/schema.ts must contain only the schema declaration: optional imports, optional const declarations, then "export default schema({ \u2026 })". Found "${exp.value}".`
|
|
4593
5048
|
);
|
|
4594
5049
|
}
|
|
4595
5050
|
const def = r.expectIdent('after "export"');
|