@forgeailab/create-spark 0.1.3 → 0.2.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/package.json +3 -3
- package/packs/README.md +22 -24
- package/packs/ai-anthropic/files/lib/anthropic.ts +46 -3
- package/packs/ai-anthropic/pack.toml +2 -3
- package/packs/auth-better-auth/files/app/api/auth/[...all]/route.ts +2 -2
- package/packs/auth-better-auth/files/lib/auth.ts +40 -1
- package/packs/auth-better-auth/pack.toml +1 -5
- package/packs/auth-better-auth-pg/files/app/api/auth/[...all]/route.ts +2 -2
- package/packs/auth-better-auth-pg/files/lib/auth.ts +40 -1
- package/packs/auth-better-auth-pg/pack.toml +1 -5
- package/packs/payments-stripe/files/lib/stripe.ts +104 -6
- package/packs/payments-stripe/pack.toml +1 -5
- package/packs/sync-zero/files/components/ZeroProvider.tsx +11 -1
- package/packs/sync-zero/files/lib/zero/client.ts +3 -3
- package/packs/sync-zero/files/lib/zero/schema.ts +15 -2
- package/packs/sync-zero/pack.toml +0 -4
- package/scripts/sync-skills.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeailab/create-spark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Interactive scaffolder for spark projects with guided pack picker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"create-spark": "./src/cli.ts"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@forgeailab/spark": "^0.
|
|
24
|
-
"@forgeailab/spark-schema": "^0.
|
|
23
|
+
"@forgeailab/spark": "^0.2.0",
|
|
24
|
+
"@forgeailab/spark-schema": "^0.2.0",
|
|
25
25
|
"@clack/prompts": "latest",
|
|
26
26
|
"citty": "latest",
|
|
27
27
|
"picocolors": "latest"
|
package/packs/README.md
CHANGED
|
@@ -91,32 +91,30 @@ See `packs/example/pack.toml` for a manifest that exercises every field.
|
|
|
91
91
|
|
|
92
92
|
## v1 catalog
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
| `auth-
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `db-
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `testing-playwright` | testing | copy | — |
|
|
113
|
-
| `deploy-vercel` | deploy | copy | — |
|
|
94
|
+
| Pack | Category |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `auth-better-auth` | auth |
|
|
97
|
+
| `auth-better-auth-pg` | auth |
|
|
98
|
+
| `auth-supabase` | auth |
|
|
99
|
+
| `db-sqlite` | db |
|
|
100
|
+
| `db-postgres` | db |
|
|
101
|
+
| `db-supabase` | db |
|
|
102
|
+
| `sync-zero` | infra |
|
|
103
|
+
| `payments-stripe` | payments |
|
|
104
|
+
| `ai-anthropic` | ai |
|
|
105
|
+
| `ai-openai` | ai |
|
|
106
|
+
| `ui-shadcn` | ui |
|
|
107
|
+
| `email-resend` | email |
|
|
108
|
+
| `analytics-posthog` | analytics |
|
|
109
|
+
| `docker-compose-dev` | infra |
|
|
110
|
+
| `testing-playwright` | testing |
|
|
111
|
+
| `deploy-vercel` | deploy |
|
|
114
112
|
|
|
115
113
|
### Picking a db + auth pair
|
|
116
114
|
|
|
117
|
-
`auth-better-auth` and `auth-better-auth-pg` share the same
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
`auth-better-auth` and `auth-better-auth-pg` share the same Better Auth factory
|
|
116
|
+
code in their generated `lib/auth.ts` templates — they differ only in the
|
|
117
|
+
`provider:` handed to `drizzleAdapter`. Pair them:
|
|
120
118
|
|
|
121
119
|
- `db-sqlite` + `auth-better-auth` — fastest path, single file db, no infra.
|
|
122
120
|
- `db-postgres` + `auth-better-auth-pg` — production-shaped, **required for `sync-zero`** (Zero needs Postgres logical replication).
|
|
@@ -127,6 +125,6 @@ installing both. Mixing wrong pairs (e.g. `db-sqlite` + `auth-better-auth-pg`)
|
|
|
127
125
|
typechecks but fails at runtime — the drizzle adapter will reject sqlite tables
|
|
128
126
|
with `provider: 'pg'`.
|
|
129
127
|
|
|
130
|
-
The
|
|
128
|
+
The copy-mode packs were authored against [`reference/full-stack-saas/`](../reference/full-stack-saas/) — the canonical integration showing the copied templates working together. When debugging one of these packs, start there.
|
|
131
129
|
|
|
132
130
|
See the root `README.md` for the catalog summary.
|
|
@@ -1,5 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
|
|
3
|
+
export function createAnthropicClient(
|
|
4
|
+
apiKey: string,
|
|
5
|
+
options?: ConstructorParameters<typeof Anthropic>[0],
|
|
6
|
+
): Anthropic {
|
|
7
|
+
return new Anthropic({ apiKey, ...options });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
|
|
12
|
+
function isContentBlockDeltaEvent(event: unknown): event is { type: 'content_block_delta' } {
|
|
13
|
+
return (
|
|
14
|
+
typeof event === 'object' &&
|
|
15
|
+
event !== null &&
|
|
16
|
+
(event as { type?: unknown }).type === 'content_block_delta'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function encodeSse(payload: unknown) {
|
|
21
|
+
return encoder.encode(`data: ${JSON.stringify(payload)}\n\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function streamResponse(
|
|
25
|
+
client: Anthropic,
|
|
26
|
+
params: Parameters<Anthropic['messages']['stream']>[0],
|
|
27
|
+
): ReadableStream<Uint8Array> {
|
|
28
|
+
return new ReadableStream<Uint8Array>({
|
|
29
|
+
async start(controller) {
|
|
30
|
+
try {
|
|
31
|
+
const stream = client.messages.stream(params);
|
|
32
|
+
|
|
33
|
+
for await (const event of stream) {
|
|
34
|
+
if (isContentBlockDeltaEvent(event)) {
|
|
35
|
+
controller.enqueue(encodeSse(event));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
|
40
|
+
controller.close();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
controller.error(error);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
3
47
|
|
|
4
48
|
function requireEnv(name: string): string {
|
|
5
49
|
const value = process.env[name];
|
|
@@ -11,5 +55,4 @@ function requireEnv(name: string): string {
|
|
|
11
55
|
|
|
12
56
|
export const anthropic = createAnthropicClient(requireEnv('ANTHROPIC_API_KEY'));
|
|
13
57
|
|
|
14
|
-
export { streamResponse };
|
|
15
58
|
export type AnthropicChatMessage = Anthropic.Messages.MessageParam;
|
|
@@ -8,9 +8,8 @@ conflicts = []
|
|
|
8
8
|
requires_runtime = ["server"]
|
|
9
9
|
compatible_scaffolds = []
|
|
10
10
|
|
|
11
|
-
[
|
|
12
|
-
|
|
13
|
-
version = "^0.1"
|
|
11
|
+
[dependencies]
|
|
12
|
+
runtime = ["@anthropic-ai/sdk"]
|
|
14
13
|
|
|
15
14
|
[env]
|
|
16
15
|
required = ["ANTHROPIC_API_KEY"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toNextJsHandler } from 'better-auth/next-js';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
|
|
4
|
-
export const { GET, POST } =
|
|
4
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
@@ -1,4 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { betterAuth } from 'better-auth';
|
|
2
|
+
|
|
3
|
+
type BetterAuthOptions = Parameters<typeof betterAuth>[0];
|
|
4
|
+
|
|
5
|
+
export type AuthInstance = ReturnType<typeof betterAuth>;
|
|
6
|
+
|
|
7
|
+
export type CreateBetterAuthOptions = {
|
|
8
|
+
adapter: BetterAuthOptions['database'];
|
|
9
|
+
basePath?: BetterAuthOptions['basePath'];
|
|
10
|
+
plugins?: BetterAuthOptions['plugins'];
|
|
11
|
+
emailAndPassword?: BetterAuthOptions['emailAndPassword'];
|
|
12
|
+
socialProviders?: BetterAuthOptions['socialProviders'];
|
|
13
|
+
secret?: BetterAuthOptions['secret'];
|
|
14
|
+
baseURL?: BetterAuthOptions['baseURL'];
|
|
15
|
+
trustedOrigins?: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function createBetterAuth({
|
|
19
|
+
adapter,
|
|
20
|
+
basePath,
|
|
21
|
+
plugins,
|
|
22
|
+
emailAndPassword,
|
|
23
|
+
socialProviders,
|
|
24
|
+
secret,
|
|
25
|
+
baseURL,
|
|
26
|
+
trustedOrigins,
|
|
27
|
+
}: CreateBetterAuthOptions) {
|
|
28
|
+
const authOptions: BetterAuthOptions = {
|
|
29
|
+
database: adapter,
|
|
30
|
+
basePath,
|
|
31
|
+
plugins,
|
|
32
|
+
emailAndPassword,
|
|
33
|
+
socialProviders,
|
|
34
|
+
secret,
|
|
35
|
+
baseURL,
|
|
36
|
+
trustedOrigins: trustedOrigins ?? (typeof baseURL === 'string' ? [baseURL] : undefined),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return betterAuth(authOptions);
|
|
40
|
+
}
|
|
2
41
|
|
|
3
42
|
// Wire your database adapter here. Example (drizzle + sqlite):
|
|
4
43
|
//
|
|
@@ -8,12 +8,8 @@ conflicts = ["auth"]
|
|
|
8
8
|
requires_runtime = ["server"]
|
|
9
9
|
compatible_scaffolds = ["nextjs"]
|
|
10
10
|
|
|
11
|
-
[runtime_package]
|
|
12
|
-
package = "@forgeailab/spark-auth-better-auth"
|
|
13
|
-
version = "^0.1"
|
|
14
|
-
|
|
15
11
|
[dependencies]
|
|
16
|
-
runtime = ["@better-auth/drizzle-adapter"]
|
|
12
|
+
runtime = ["@better-auth/drizzle-adapter", "better-auth"]
|
|
17
13
|
|
|
18
14
|
[env]
|
|
19
15
|
required = ["BETTER_AUTH_SECRET", "BETTER_AUTH_URL"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toNextJsHandler } from 'better-auth/next-js';
|
|
2
2
|
import { auth } from '@/lib/auth';
|
|
3
3
|
|
|
4
|
-
export const { GET, POST } =
|
|
4
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
import { drizzleAdapter } from '@better-auth/drizzle-adapter';
|
|
2
|
-
import {
|
|
2
|
+
import { betterAuth } from 'better-auth';
|
|
3
3
|
import { db } from '@/lib/db';
|
|
4
4
|
import * as schema from '@/lib/db/schema';
|
|
5
5
|
|
|
6
|
+
type BetterAuthOptions = Parameters<typeof betterAuth>[0];
|
|
7
|
+
|
|
8
|
+
export type AuthInstance = ReturnType<typeof betterAuth>;
|
|
9
|
+
|
|
10
|
+
export type CreateBetterAuthOptions = {
|
|
11
|
+
adapter: BetterAuthOptions['database'];
|
|
12
|
+
basePath?: BetterAuthOptions['basePath'];
|
|
13
|
+
plugins?: BetterAuthOptions['plugins'];
|
|
14
|
+
emailAndPassword?: BetterAuthOptions['emailAndPassword'];
|
|
15
|
+
socialProviders?: BetterAuthOptions['socialProviders'];
|
|
16
|
+
secret?: BetterAuthOptions['secret'];
|
|
17
|
+
baseURL?: BetterAuthOptions['baseURL'];
|
|
18
|
+
trustedOrigins?: string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function createBetterAuth({
|
|
22
|
+
adapter,
|
|
23
|
+
basePath,
|
|
24
|
+
plugins,
|
|
25
|
+
emailAndPassword,
|
|
26
|
+
socialProviders,
|
|
27
|
+
secret,
|
|
28
|
+
baseURL,
|
|
29
|
+
trustedOrigins,
|
|
30
|
+
}: CreateBetterAuthOptions) {
|
|
31
|
+
const authOptions: BetterAuthOptions = {
|
|
32
|
+
database: adapter,
|
|
33
|
+
basePath,
|
|
34
|
+
plugins,
|
|
35
|
+
emailAndPassword,
|
|
36
|
+
socialProviders,
|
|
37
|
+
secret,
|
|
38
|
+
baseURL,
|
|
39
|
+
trustedOrigins: trustedOrigins ?? (typeof baseURL === 'string' ? [baseURL] : undefined),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return betterAuth(authOptions);
|
|
43
|
+
}
|
|
44
|
+
|
|
6
45
|
// Postgres-flavored Better Auth wiring. Pair this pack with `db-postgres`
|
|
7
46
|
// (or `db-supabase`), and add the four Better Auth tables to your
|
|
8
47
|
// `lib/db/schema.ts`: `user`, `session`, `account`, `verification`. Snake-case
|
|
@@ -8,12 +8,8 @@ conflicts = ["auth"]
|
|
|
8
8
|
requires_runtime = ["server"]
|
|
9
9
|
compatible_scaffolds = ["nextjs"]
|
|
10
10
|
|
|
11
|
-
[runtime_package]
|
|
12
|
-
package = "@forgeailab/spark-auth-better-auth"
|
|
13
|
-
version = "^0.1"
|
|
14
|
-
|
|
15
11
|
[dependencies]
|
|
16
|
-
runtime = ["@better-auth/drizzle-adapter"]
|
|
12
|
+
runtime = ["@better-auth/drizzle-adapter", "better-auth"]
|
|
17
13
|
|
|
18
14
|
[env]
|
|
19
15
|
required = ["BETTER_AUTH_SECRET", "BETTER_AUTH_URL"]
|
|
@@ -1,9 +1,107 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
|
|
3
|
+
type StripeClientOptions = ConstructorParameters<typeof Stripe>[1];
|
|
4
|
+
|
|
5
|
+
declare module 'stripe' {
|
|
6
|
+
namespace Stripe {
|
|
7
|
+
export type StripeConstructorOptions = StripeClientOptions;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createStripeClient(
|
|
12
|
+
secretKey: string,
|
|
13
|
+
options?: Stripe.StripeConstructorOptions,
|
|
14
|
+
): Stripe {
|
|
15
|
+
return new Stripe(secretKey, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CheckoutSessionCreateParams = NonNullable<
|
|
19
|
+
Parameters<Stripe['checkout']['sessions']['create']>[0]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export type CreateCheckoutSessionInput = {
|
|
23
|
+
priceId: string;
|
|
24
|
+
customerId?: string;
|
|
25
|
+
customerEmail?: string;
|
|
26
|
+
successUrl: string;
|
|
27
|
+
cancelUrl: string;
|
|
28
|
+
mode?: CheckoutSessionCreateParams['mode'];
|
|
29
|
+
metadata?: Record<string, string>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function createStripeCheckoutSession(
|
|
33
|
+
stripeClient: Stripe,
|
|
34
|
+
{
|
|
35
|
+
priceId,
|
|
36
|
+
customerId,
|
|
37
|
+
customerEmail,
|
|
38
|
+
successUrl,
|
|
39
|
+
cancelUrl,
|
|
40
|
+
mode = 'subscription',
|
|
41
|
+
metadata,
|
|
42
|
+
}: CreateCheckoutSessionInput,
|
|
43
|
+
): Promise<{ url: string; sessionId: string }> {
|
|
44
|
+
if (!customerId && !customerEmail) {
|
|
45
|
+
throw new Error('customerId or customerEmail is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const session = await stripeClient.checkout.sessions.create({
|
|
49
|
+
mode,
|
|
50
|
+
customer: customerId,
|
|
51
|
+
customer_email: customerId ? undefined : customerEmail,
|
|
52
|
+
line_items: [
|
|
53
|
+
{
|
|
54
|
+
price: priceId,
|
|
55
|
+
quantity: 1,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
allow_promotion_codes: true,
|
|
59
|
+
success_url: successUrl,
|
|
60
|
+
cancel_url: cancelUrl,
|
|
61
|
+
metadata,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!session.url) {
|
|
65
|
+
throw new Error('Stripe did not return a checkout URL');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
url: session.url,
|
|
70
|
+
sessionId: session.id,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type VerifyWebhookSignatureInput = {
|
|
75
|
+
payload: string | Buffer;
|
|
76
|
+
signatureHeader: string;
|
|
77
|
+
secret: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function verifyStripeWebhookSignature(
|
|
81
|
+
stripeClient: Stripe,
|
|
82
|
+
{ payload, signatureHeader, secret }: VerifyWebhookSignatureInput,
|
|
83
|
+
): Promise<Stripe.Event> {
|
|
84
|
+
return stripeClient.webhooks.constructEventAsync(payload, signatureHeader, secret);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type CreateBillingPortalSessionInput = {
|
|
88
|
+
customerId: string;
|
|
89
|
+
returnUrl: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
async function createStripeBillingPortalSession(
|
|
93
|
+
stripeClient: Stripe,
|
|
94
|
+
{ customerId, returnUrl }: CreateBillingPortalSessionInput,
|
|
95
|
+
): Promise<{ url: string }> {
|
|
96
|
+
const session = await stripeClient.billingPortal.sessions.create({
|
|
97
|
+
customer: customerId,
|
|
98
|
+
return_url: returnUrl,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
url: session.url,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
7
105
|
|
|
8
106
|
function requireEnv(name: string): string {
|
|
9
107
|
const value = process.env[name];
|
|
@@ -8,12 +8,8 @@ conflicts = ["payments"]
|
|
|
8
8
|
requires_runtime = ["server"]
|
|
9
9
|
compatible_scaffolds = ["nextjs"]
|
|
10
10
|
|
|
11
|
-
[runtime_package]
|
|
12
|
-
package = "@forgeailab/spark-stripe-helpers"
|
|
13
|
-
version = "^0.1"
|
|
14
|
-
|
|
15
11
|
[dependencies]
|
|
16
|
-
runtime = ["@stripe/stripe-js"]
|
|
12
|
+
runtime = ["@stripe/stripe-js", "stripe"]
|
|
17
13
|
|
|
18
14
|
[env]
|
|
19
15
|
required = [
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import type { ZeroOptions } from '@rocicorp/zero';
|
|
5
|
+
import { ZeroProvider as RocicorpZeroProvider } from '@rocicorp/zero/react';
|
|
6
|
+
|
|
7
|
+
type ZeroProviderProps = ZeroOptions & {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function ZeroProvider({ children, ...options }: ZeroProviderProps) {
|
|
12
|
+
return <RocicorpZeroProvider {...options}>{children}</RocicorpZeroProvider>;
|
|
13
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import type { ZeroOptions } from '@
|
|
3
|
+
import { Zero } from '@rocicorp/zero';
|
|
4
|
+
import type { ZeroOptions } from '@rocicorp/zero';
|
|
5
5
|
import { schema } from './schema';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_ZERO_URL = 'http://localhost:4848';
|
|
@@ -14,5 +14,5 @@ export function createZeroOptions(): ZeroOptions {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function createZeroClient(options: ZeroOptions = createZeroOptions()) {
|
|
17
|
-
return
|
|
17
|
+
return new Zero(options);
|
|
18
18
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ANYONE_CAN,
|
|
3
|
+
createBuilder,
|
|
4
|
+
createSchema,
|
|
5
|
+
definePermissions,
|
|
6
|
+
number,
|
|
7
|
+
string,
|
|
8
|
+
table,
|
|
9
|
+
} from '@rocicorp/zero';
|
|
2
10
|
|
|
3
11
|
// Example schema. Replace with your app's tables.
|
|
4
12
|
const users = table('user')
|
|
@@ -10,8 +18,13 @@ const users = table('user')
|
|
|
10
18
|
})
|
|
11
19
|
.primaryKey('id');
|
|
12
20
|
|
|
13
|
-
export const
|
|
21
|
+
export const schema = createSchema({
|
|
14
22
|
tables: [users],
|
|
15
23
|
});
|
|
24
|
+
export const zql = createBuilder(schema);
|
|
16
25
|
|
|
17
26
|
export type Schema = typeof schema;
|
|
27
|
+
|
|
28
|
+
export const permissions = definePermissions<unknown, Schema>(schema, () => ({
|
|
29
|
+
user: { row: { select: ANYONE_CAN } },
|
|
30
|
+
}));
|
package/scripts/sync-skills.ts
CHANGED