clawfire 0.1.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.
Files changed (87) hide show
  1. package/README.md +182 -0
  2. package/dist/admin.cjs +309 -0
  3. package/dist/admin.cjs.map +1 -0
  4. package/dist/admin.d.cts +93 -0
  5. package/dist/admin.d.ts +93 -0
  6. package/dist/admin.js +274 -0
  7. package/dist/admin.js.map +1 -0
  8. package/dist/auth-DQ3cifhb.d.cts +55 -0
  9. package/dist/auth-DtnUPbXT.d.ts +55 -0
  10. package/dist/chunk-37Y2XI7X.js +75 -0
  11. package/dist/chunk-YGIPORYL.js +339 -0
  12. package/dist/cli.js +241 -0
  13. package/dist/client.cjs +97 -0
  14. package/dist/client.cjs.map +1 -0
  15. package/dist/client.d.cts +4 -0
  16. package/dist/client.d.ts +4 -0
  17. package/dist/client.js +68 -0
  18. package/dist/client.js.map +1 -0
  19. package/dist/codegen.cjs +648 -0
  20. package/dist/codegen.cjs.map +1 -0
  21. package/dist/codegen.d.cts +25 -0
  22. package/dist/codegen.d.ts +25 -0
  23. package/dist/codegen.js +617 -0
  24. package/dist/codegen.js.map +1 -0
  25. package/dist/config-QMBJRn9G.d.cts +46 -0
  26. package/dist/config-QMBJRn9G.d.ts +46 -0
  27. package/dist/dev-server-QAVWINAT.js +973 -0
  28. package/dist/dev.cjs +1388 -0
  29. package/dist/dev.cjs.map +1 -0
  30. package/dist/dev.d.cts +111 -0
  31. package/dist/dev.d.ts +111 -0
  32. package/dist/dev.js +1349 -0
  33. package/dist/dev.js.map +1 -0
  34. package/dist/discover-BPMAZFBD.js +9 -0
  35. package/dist/discover-DYNqz_ym.d.cts +28 -0
  36. package/dist/discover-DYNqz_ym.d.ts +28 -0
  37. package/dist/errors-s_mP7rs9.d.cts +33 -0
  38. package/dist/errors-s_mP7rs9.d.ts +33 -0
  39. package/dist/functions.cjs +1156 -0
  40. package/dist/functions.cjs.map +1 -0
  41. package/dist/functions.d.cts +115 -0
  42. package/dist/functions.d.ts +115 -0
  43. package/dist/functions.js +1108 -0
  44. package/dist/functions.js.map +1 -0
  45. package/dist/hosting-7WVFHAYJ.js +85 -0
  46. package/dist/html-PCUCJGBH.js +7 -0
  47. package/dist/index.cjs +349 -0
  48. package/dist/index.cjs.map +1 -0
  49. package/dist/index.d.cts +22 -0
  50. package/dist/index.d.ts +22 -0
  51. package/dist/index.js +312 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/playground.cjs +364 -0
  54. package/dist/playground.cjs.map +1 -0
  55. package/dist/playground.d.cts +12 -0
  56. package/dist/playground.d.ts +12 -0
  57. package/dist/playground.js +337 -0
  58. package/dist/playground.js.map +1 -0
  59. package/dist/router-BVB_I-tu.d.ts +65 -0
  60. package/dist/router-Cikk8Heq.d.cts +65 -0
  61. package/dist/schema-BJsictSV.d.cts +172 -0
  62. package/dist/schema-BJsictSV.d.ts +172 -0
  63. package/package.json +150 -0
  64. package/templates/CLAUDE.md +71 -0
  65. package/templates/app/routes/auth/login.ts +35 -0
  66. package/templates/app/routes/health.ts +20 -0
  67. package/templates/app/schemas/user.ts +26 -0
  68. package/templates/clawfire.config.ts +25 -0
  69. package/templates/functions/index.ts +43 -0
  70. package/templates/starter/.claude/skills/clawfire-api/SKILL.md +131 -0
  71. package/templates/starter/.claude/skills/clawfire-auth/SKILL.md +111 -0
  72. package/templates/starter/.claude/skills/clawfire-deploy/SKILL.md +95 -0
  73. package/templates/starter/.claude/skills/clawfire-diagnose/SKILL.md +99 -0
  74. package/templates/starter/.claude/skills/clawfire-model/SKILL.md +128 -0
  75. package/templates/starter/CLAUDE.md +227 -0
  76. package/templates/starter/app/routes/health.ts +20 -0
  77. package/templates/starter/app/routes/todos/create.ts +25 -0
  78. package/templates/starter/app/routes/todos/delete.ts +20 -0
  79. package/templates/starter/app/routes/todos/list.ts +26 -0
  80. package/templates/starter/app/routes/todos/update.ts +32 -0
  81. package/templates/starter/app/schemas/todo.ts +16 -0
  82. package/templates/starter/app/store.ts +56 -0
  83. package/templates/starter/clawfire.config.ts +25 -0
  84. package/templates/starter/dev.ts +12 -0
  85. package/templates/starter/package.json +19 -0
  86. package/templates/starter/public/index.html +365 -0
  87. package/templates/starter/tsconfig.json +17 -0
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # Clawfire
2
+
3
+ **AI-First Firebase App Framework — Speak. Build. Deploy.**
4
+
5
+ Clawfire는 "말하면 완성되는 Firebase 앱 플랫폼"입니다. 코드를 작성하지 않고, AI(Claude Skills)에게 말하며 앱을 만듭니다.
6
+
7
+ ## Features
8
+
9
+ - **Contract-based API** — Zod 스키마로 input/output 계약 정의
10
+ - **File-based Routing** — `app/routes/` 디렉터리 = API 경로
11
+ - **Auto Client Generation** — 타입 안전한 `api.xxx.yyy()` 클라이언트 자동 생성
12
+ - **Firebase-Only Stack** — Firestore, Auth, Functions, Hosting 완전 통합
13
+ - **Security by Default** — 입력 검증, CORS, Rate Limit, 로그 마스킹 기본 제공
14
+ - **AI Skills System** — `/clawfire-*` 커맨드로 프로젝트 관리
15
+ - **API Playground** — 웹 기반 API 탐색기 내장
16
+ - **Auto Security Rules** — 모델 정의에서 Firestore Rules 자동 생성
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # 새 프로젝트 초기화
22
+ npx clawfire init
23
+
24
+ # 또는 기존 프로젝트에 설치
25
+ npm install clawfire
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Define a Model
31
+
32
+ ```typescript
33
+ // app/schemas/product.ts
34
+ import { defineModel } from "clawfire";
35
+
36
+ export const Product = defineModel({
37
+ collection: "products",
38
+ fields: {
39
+ name: { type: "string", required: true },
40
+ price: { type: "number", required: true },
41
+ category: { type: "string", enum: ["electronics", "clothing"] },
42
+ },
43
+ timestamps: true,
44
+ rules: {
45
+ read: "public",
46
+ create: "role",
47
+ createRoles: ["admin"],
48
+ },
49
+ });
50
+ ```
51
+
52
+ ### Define an API
53
+
54
+ ```typescript
55
+ // app/routes/products/list.ts
56
+ import { defineAPI, z } from "clawfire";
57
+
58
+ export default defineAPI({
59
+ input: z.object({
60
+ category: z.string().optional(),
61
+ limit: z.number().default(20),
62
+ }),
63
+ output: z.object({
64
+ products: z.array(z.object({
65
+ id: z.string(),
66
+ name: z.string(),
67
+ price: z.number(),
68
+ })),
69
+ hasMore: z.boolean(),
70
+ }),
71
+ meta: {
72
+ description: "상품 목록 조회",
73
+ auth: "public",
74
+ tags: ["products"],
75
+ },
76
+ handler: async (input, ctx) => {
77
+ // Your business logic here
78
+ return { products: [], hasMore: false };
79
+ },
80
+ });
81
+ ```
82
+
83
+ ### Server Setup (Firebase Functions)
84
+
85
+ ```typescript
86
+ // functions/index.ts
87
+ import * as admin from "firebase-admin";
88
+ import * as functions from "firebase-functions";
89
+ import { createRouter, createAdminDB, createSecurityMiddleware } from "clawfire/functions";
90
+
91
+ admin.initializeApp();
92
+ const db = createAdminDB(admin.firestore());
93
+
94
+ const router = createRouter({
95
+ auth: admin.auth(),
96
+ cors: ["https://myapp.web.app"],
97
+ middleware: createSecurityMiddleware(),
98
+ });
99
+
100
+ // Register routes
101
+ import listProducts from "../app/routes/products/list";
102
+ router.register("/products/list", listProducts);
103
+
104
+ export const api = functions.https.onRequest((req, res) => {
105
+ router.handleRequest(req as any, res as any);
106
+ });
107
+ ```
108
+
109
+ ### Client Usage
110
+
111
+ ```typescript
112
+ // Generated api-client.ts provides typed access
113
+ import { api, configureClient } from "./generated/api-client";
114
+ import { getAuth } from "firebase/auth";
115
+
116
+ configureClient("https://us-central1-myapp.cloudfunctions.net", async () => {
117
+ const user = getAuth().currentUser;
118
+ return user ? user.getIdToken() : null;
119
+ });
120
+
121
+ const result = await api.products.list({ category: "electronics", limit: 10 });
122
+ ```
123
+
124
+ ## Project Structure
125
+
126
+ ```
127
+ app/
128
+ routes/ API route handlers (file-based routing)
129
+ schemas/ Model definitions (Firestore collections)
130
+ generated/ Auto-generated files (DO NOT EDIT)
131
+ api-client.ts Typed API client
132
+ manifest.json API manifest
133
+ functions/ Firebase Functions entry point
134
+ public/ Static files for Firebase Hosting
135
+ firebase.json Firebase config
136
+ firestore.rules Firestore security rules
137
+ clawfire.config.ts Clawfire configuration
138
+ ```
139
+
140
+ ## AI Skills (Claude Code)
141
+
142
+ | Command | Description |
143
+ |---------|-------------|
144
+ | `/clawfire-init` | Initialize project / connect Firebase |
145
+ | `/clawfire-model` | Create/modify data models |
146
+ | `/clawfire-api` | Create/modify APIs |
147
+ | `/clawfire-auth` | Configure authentication |
148
+ | `/clawfire-deploy` | Deploy to Firebase |
149
+ | `/clawfire-diagnose` | Diagnose and fix issues |
150
+
151
+ ## Auth Levels
152
+
153
+ | Level | Description |
154
+ |-------|-------------|
155
+ | `public` | No authentication required |
156
+ | `authenticated` | Login required |
157
+ | `role` | Specific role required |
158
+ | `reauth` | Recent re-authentication required |
159
+
160
+ ## Exports
161
+
162
+ | Path | Description |
163
+ |------|-------------|
164
+ | `clawfire` | Core types, defineAPI, defineModel, Zod |
165
+ | `clawfire/functions` | Server-side: Router, DB, Auth, Security |
166
+ | `clawfire/client` | Client-side: Auth helpers |
167
+ | `clawfire/admin` | Admin: Rules, Indexes, User management |
168
+ | `clawfire/codegen` | Code generation utilities |
169
+ | `clawfire/playground` | Playground HTML generator |
170
+
171
+ ## Tech Stack (Fixed)
172
+
173
+ - **DB**: Firestore
174
+ - **Auth**: Firebase Auth
175
+ - **Hosting**: Firebase Hosting
176
+ - **Backend**: Firebase Functions
177
+ - **Schema**: Zod
178
+ - **Language**: TypeScript
179
+
180
+ ## License
181
+
182
+ MIT
package/dist/admin.cjs ADDED
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/admin.ts
21
+ var admin_exports = {};
22
+ __export(admin_exports, {
23
+ defineModel: () => defineModel,
24
+ generateDefaultRules: () => generateDefaultRules,
25
+ generateFirebaseJson: () => generateFirebaseJson,
26
+ generateFirestoreIndexes: () => generateFirestoreIndexes,
27
+ generateFirestoreRules: () => generateFirestoreRules,
28
+ getUserRole: () => getUserRole,
29
+ modelToZodSchema: () => modelToZodSchema,
30
+ setCustomClaims: () => setCustomClaims,
31
+ setUserRole: () => setUserRole
32
+ });
33
+ module.exports = __toCommonJS(admin_exports);
34
+
35
+ // src/firebase/rules.ts
36
+ function generateFirestoreRules(models) {
37
+ const rules = [];
38
+ rules.push("rules_version = '2';");
39
+ rules.push("service cloud.firestore {");
40
+ rules.push(" match /databases/{database}/documents {");
41
+ rules.push("");
42
+ rules.push(" // \u2500\u2500 Helper Functions \u2500\u2500");
43
+ rules.push(" function isAuthenticated() {");
44
+ rules.push(" return request.auth != null;");
45
+ rules.push(" }");
46
+ rules.push("");
47
+ rules.push(" function isOwner(userId) {");
48
+ rules.push(" return isAuthenticated() && request.auth.uid == userId;");
49
+ rules.push(" }");
50
+ rules.push("");
51
+ rules.push(" function hasRole(role) {");
52
+ rules.push(" return isAuthenticated() && request.auth.token.role == role;");
53
+ rules.push(" }");
54
+ rules.push("");
55
+ rules.push(" function hasAnyRole(roles) {");
56
+ rules.push(" return isAuthenticated() && request.auth.token.role in roles;");
57
+ rules.push(" }");
58
+ rules.push("");
59
+ rules.push(" function isRecentAuth() {");
60
+ rules.push(" return request.auth.token.auth_time > (request.time - duration.value(5, 'm'));");
61
+ rules.push(" }");
62
+ rules.push("");
63
+ for (const [name, model] of Object.entries(models)) {
64
+ rules.push(` // \u2500\u2500 ${name} \u2500\u2500`);
65
+ rules.push(` match /${model.collection}/{docId} {`);
66
+ const modelRules = model.rules || { read: "authenticated", create: "authenticated", update: "authenticated", delete: "authenticated" };
67
+ rules.push(` allow read: if ${buildRuleCondition(modelRules, "read", model)};`);
68
+ rules.push(` allow create: if ${buildRuleCondition(modelRules, "create", model)};`);
69
+ rules.push(` allow update: if ${buildRuleCondition(modelRules, "update", model)};`);
70
+ rules.push(` allow delete: if ${buildRuleCondition(modelRules, "delete", model)};`);
71
+ if (model.subcollections) {
72
+ for (const [subName, subModel] of Object.entries(model.subcollections)) {
73
+ rules.push("");
74
+ rules.push(` // ${subName}`);
75
+ rules.push(` match /${subModel.collection}/{subDocId} {`);
76
+ const subRules = subModel.rules || { read: "authenticated", create: "authenticated", update: "authenticated", delete: "authenticated" };
77
+ rules.push(` allow read: if ${buildRuleCondition(subRules, "read", subModel)};`);
78
+ rules.push(` allow create: if ${buildRuleCondition(subRules, "create", subModel)};`);
79
+ rules.push(` allow update: if ${buildRuleCondition(subRules, "update", subModel)};`);
80
+ rules.push(` allow delete: if ${buildRuleCondition(subRules, "delete", subModel)};`);
81
+ rules.push(" }");
82
+ }
83
+ }
84
+ rules.push(" }");
85
+ rules.push("");
86
+ }
87
+ rules.push(" }");
88
+ rules.push("}");
89
+ return rules.join("\n");
90
+ }
91
+ function buildRuleCondition(rules, operation, model) {
92
+ const level = rules[operation] || "authenticated";
93
+ const roles = rules[`${operation}Roles`];
94
+ const ownerField = rules.ownerField;
95
+ const conditions = [];
96
+ switch (level) {
97
+ case "public":
98
+ return "true";
99
+ case "authenticated":
100
+ conditions.push("isAuthenticated()");
101
+ break;
102
+ case "role":
103
+ if (roles && roles.length > 0) {
104
+ if (roles.length === 1) {
105
+ conditions.push(`hasRole('${roles[0]}')`);
106
+ } else {
107
+ conditions.push(`hasAnyRole(${JSON.stringify(roles)})`);
108
+ }
109
+ } else {
110
+ conditions.push("isAuthenticated()");
111
+ }
112
+ break;
113
+ case "reauth":
114
+ conditions.push("isAuthenticated()");
115
+ conditions.push("isRecentAuth()");
116
+ break;
117
+ }
118
+ if (ownerField && (operation === "read" || operation === "update" || operation === "delete")) {
119
+ const ownerCondition = operation === "read" ? `resource.data.${ownerField} == request.auth.uid` : `resource.data.${ownerField} == request.auth.uid`;
120
+ if (conditions.length > 0) {
121
+ return `(${conditions.join(" && ")}) || (isAuthenticated() && ${ownerCondition})`;
122
+ }
123
+ return `isAuthenticated() && ${ownerCondition}`;
124
+ }
125
+ if (ownerField && operation === "create") {
126
+ conditions.push(`request.resource.data.${ownerField} == request.auth.uid`);
127
+ }
128
+ return conditions.join(" && ") || "false";
129
+ }
130
+ function generateFirestoreIndexes(models) {
131
+ const indexes = [];
132
+ for (const model of Object.values(models)) {
133
+ if (model.indexes) {
134
+ for (const index of model.indexes) {
135
+ indexes.push({
136
+ collectionGroup: model.collection,
137
+ queryScope: "COLLECTION",
138
+ fields: index.fields.map((f) => ({
139
+ fieldPath: f.field,
140
+ order: (f.order || "asc").toUpperCase()
141
+ }))
142
+ });
143
+ }
144
+ }
145
+ }
146
+ return { indexes };
147
+ }
148
+
149
+ // src/firebase/hosting.ts
150
+ function generateFirebaseJson(config) {
151
+ return {
152
+ hosting: {
153
+ public: "public",
154
+ ignore: ["firebase.json", "**/.*", "**/node_modules/**"],
155
+ rewrites: [
156
+ {
157
+ source: "/api/**",
158
+ function: "api"
159
+ },
160
+ {
161
+ source: "**",
162
+ destination: "/index.html"
163
+ }
164
+ ],
165
+ headers: [
166
+ {
167
+ source: "/api/**",
168
+ headers: [
169
+ { key: "Cache-Control", value: "no-store" },
170
+ { key: "X-Content-Type-Options", value: "nosniff" },
171
+ { key: "X-Frame-Options", value: "DENY" },
172
+ { key: "X-XSS-Protection", value: "1; mode=block" },
173
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }
174
+ ]
175
+ },
176
+ {
177
+ source: "**/*.@(js|css|svg|png|jpg|jpeg|gif|ico|webp|woff|woff2)",
178
+ headers: [
179
+ { key: "Cache-Control", value: "public, max-age=31536000, immutable" }
180
+ ]
181
+ }
182
+ ]
183
+ },
184
+ functions: {
185
+ source: "functions",
186
+ runtime: "nodejs20",
187
+ codebase: "clawfire"
188
+ },
189
+ firestore: {
190
+ rules: "firestore.rules",
191
+ indexes: "firestore.indexes.json"
192
+ },
193
+ emulators: {
194
+ functions: { port: 5001 },
195
+ firestore: { port: 8080 },
196
+ auth: { port: 9099 },
197
+ hosting: { port: 5e3 },
198
+ ui: { enabled: true, port: 4e3 }
199
+ }
200
+ };
201
+ }
202
+ function generateDefaultRules() {
203
+ return `rules_version = '2';
204
+ service cloud.firestore {
205
+ match /databases/{database}/documents {
206
+
207
+ // Helper functions
208
+ function isAuthenticated() {
209
+ return request.auth != null;
210
+ }
211
+
212
+ function isOwner(userId) {
213
+ return isAuthenticated() && request.auth.uid == userId;
214
+ }
215
+
216
+ function hasRole(role) {
217
+ return isAuthenticated() && request.auth.token.role == role;
218
+ }
219
+
220
+ // Default: deny all
221
+ match /{document=**} {
222
+ allow read, write: if false;
223
+ }
224
+ }
225
+ }
226
+ `;
227
+ }
228
+
229
+ // src/firebase/auth.ts
230
+ async function setUserRole(auth, uid, role) {
231
+ const user = await auth.getUser(uid);
232
+ const currentClaims = user.customClaims || {};
233
+ await auth.setCustomUserClaims(uid, { ...currentClaims, role });
234
+ }
235
+ async function getUserRole(auth, uid) {
236
+ const user = await auth.getUser(uid);
237
+ return user.customClaims?.role;
238
+ }
239
+ async function setCustomClaims(auth, uid, claims) {
240
+ const user = await auth.getUser(uid);
241
+ const currentClaims = user.customClaims || {};
242
+ await auth.setCustomUserClaims(uid, { ...currentClaims, ...claims });
243
+ }
244
+
245
+ // src/core/schema.ts
246
+ var import_zod = require("zod");
247
+ function defineModel(definition) {
248
+ return {
249
+ timestamps: true,
250
+ ...definition
251
+ };
252
+ }
253
+ function modelToZodSchema(model) {
254
+ const shape = {};
255
+ for (const [key, field] of Object.entries(model.fields)) {
256
+ let fieldSchema = fieldToZod(field);
257
+ if (!field.required) {
258
+ fieldSchema = fieldSchema.optional();
259
+ }
260
+ shape[key] = fieldSchema;
261
+ }
262
+ if (model.timestamps) {
263
+ shape.createdAt = import_zod.z.string().datetime().optional();
264
+ shape.updatedAt = import_zod.z.string().datetime().optional();
265
+ }
266
+ if (model.softDelete) {
267
+ shape.deletedAt = import_zod.z.string().datetime().nullable().optional();
268
+ }
269
+ return import_zod.z.object(shape);
270
+ }
271
+ function fieldToZod(field) {
272
+ switch (field.type) {
273
+ case "string":
274
+ if (field.enum) return import_zod.z.enum(field.enum);
275
+ return import_zod.z.string();
276
+ case "number":
277
+ return import_zod.z.number();
278
+ case "boolean":
279
+ return import_zod.z.boolean();
280
+ case "timestamp":
281
+ return import_zod.z.string().datetime();
282
+ case "array":
283
+ if (field.items) return import_zod.z.array(fieldToZod(field.items));
284
+ return import_zod.z.array(import_zod.z.unknown());
285
+ case "map":
286
+ if (field.values) return import_zod.z.record(import_zod.z.string(), fieldToZod(field.values));
287
+ return import_zod.z.record(import_zod.z.string(), import_zod.z.unknown());
288
+ case "reference":
289
+ return import_zod.z.string();
290
+ // 참조는 문서 경로 문자열
291
+ case "geopoint":
292
+ return import_zod.z.object({ latitude: import_zod.z.number(), longitude: import_zod.z.number() });
293
+ default:
294
+ return import_zod.z.unknown();
295
+ }
296
+ }
297
+ // Annotate the CommonJS export names for ESM import in node:
298
+ 0 && (module.exports = {
299
+ defineModel,
300
+ generateDefaultRules,
301
+ generateFirebaseJson,
302
+ generateFirestoreIndexes,
303
+ generateFirestoreRules,
304
+ getUserRole,
305
+ modelToZodSchema,
306
+ setCustomClaims,
307
+ setUserRole
308
+ });
309
+ //# sourceMappingURL=admin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/admin.ts","../src/firebase/rules.ts","../src/firebase/hosting.ts","../src/firebase/auth.ts","../src/core/schema.ts"],"sourcesContent":["/**\n * Clawfire Admin Entry Point\n *\n * Rules 생성, 인덱스 생성, 모델 관리 등 관리 도구\n *\n * @example\n * ```ts\n * import { generateFirestoreRules, generateFirestoreIndexes } from \"clawfire/admin\";\n * ```\n */\n\nexport {\n generateFirestoreRules,\n generateFirestoreIndexes,\n} from \"./firebase/rules.js\";\n\nexport {\n generateFirebaseJson,\n generateDefaultRules,\n type FirebaseJsonConfig,\n} from \"./firebase/hosting.js\";\n\nexport {\n setUserRole,\n getUserRole,\n setCustomClaims,\n} from \"./firebase/auth.js\";\n\nexport {\n defineModel,\n modelToZodSchema,\n type ModelDefinition,\n type ModelField,\n type ModelIndex,\n type ModelRules,\n} from \"./core/index.js\";\n","/**\n * Clawfire Security Rules Generator\n *\n * 모델 정의에서 Firestore Security Rules 자동 생성\n */\nimport type { ModelDefinition, ModelRules, AuthLevel } from \"../core/schema.js\";\n\n/**\n * 모델 정의 배열에서 Firestore Security Rules 생성\n */\nexport function generateFirestoreRules(\n models: Record<string, ModelDefinition>,\n): string {\n const rules: string[] = [];\n rules.push(\"rules_version = '2';\");\n rules.push(\"service cloud.firestore {\");\n rules.push(\" match /databases/{database}/documents {\");\n rules.push(\"\");\n rules.push(\" // ── Helper Functions ──\");\n rules.push(\" function isAuthenticated() {\");\n rules.push(\" return request.auth != null;\");\n rules.push(\" }\");\n rules.push(\"\");\n rules.push(\" function isOwner(userId) {\");\n rules.push(\" return isAuthenticated() && request.auth.uid == userId;\");\n rules.push(\" }\");\n rules.push(\"\");\n rules.push(\" function hasRole(role) {\");\n rules.push(\" return isAuthenticated() && request.auth.token.role == role;\");\n rules.push(\" }\");\n rules.push(\"\");\n rules.push(\" function hasAnyRole(roles) {\");\n rules.push(\" return isAuthenticated() && request.auth.token.role in roles;\");\n rules.push(\" }\");\n rules.push(\"\");\n rules.push(\" function isRecentAuth() {\");\n rules.push(\" return request.auth.token.auth_time > (request.time - duration.value(5, 'm'));\");\n rules.push(\" }\");\n rules.push(\"\");\n\n for (const [name, model] of Object.entries(models)) {\n rules.push(` // ── ${name} ──`);\n rules.push(` match /${model.collection}/{docId} {`);\n\n const modelRules = model.rules || { read: \"authenticated\" as AuthLevel, create: \"authenticated\" as AuthLevel, update: \"authenticated\" as AuthLevel, delete: \"authenticated\" as AuthLevel };\n\n rules.push(` allow read: if ${buildRuleCondition(modelRules, \"read\", model)};`);\n rules.push(` allow create: if ${buildRuleCondition(modelRules, \"create\", model)};`);\n rules.push(` allow update: if ${buildRuleCondition(modelRules, \"update\", model)};`);\n rules.push(` allow delete: if ${buildRuleCondition(modelRules, \"delete\", model)};`);\n\n // 서브컬렉션\n if (model.subcollections) {\n for (const [subName, subModel] of Object.entries(model.subcollections)) {\n rules.push(\"\");\n rules.push(` // ${subName}`);\n rules.push(` match /${subModel.collection}/{subDocId} {`);\n const subRules = subModel.rules || { read: \"authenticated\" as AuthLevel, create: \"authenticated\" as AuthLevel, update: \"authenticated\" as AuthLevel, delete: \"authenticated\" as AuthLevel };\n rules.push(` allow read: if ${buildRuleCondition(subRules, \"read\", subModel)};`);\n rules.push(` allow create: if ${buildRuleCondition(subRules, \"create\", subModel)};`);\n rules.push(` allow update: if ${buildRuleCondition(subRules, \"update\", subModel)};`);\n rules.push(` allow delete: if ${buildRuleCondition(subRules, \"delete\", subModel)};`);\n rules.push(\" }\");\n }\n }\n\n rules.push(\" }\");\n rules.push(\"\");\n }\n\n rules.push(\" }\");\n rules.push(\"}\");\n\n return rules.join(\"\\n\");\n}\n\nfunction buildRuleCondition(\n rules: ModelRules,\n operation: \"read\" | \"create\" | \"update\" | \"delete\",\n model: ModelDefinition,\n): string {\n const level = rules[operation] || \"authenticated\";\n const roles = rules[`${operation}Roles` as keyof ModelRules] as string[] | undefined;\n const ownerField = rules.ownerField;\n\n const conditions: string[] = [];\n\n switch (level) {\n case \"public\":\n return \"true\";\n\n case \"authenticated\":\n conditions.push(\"isAuthenticated()\");\n break;\n\n case \"role\":\n if (roles && roles.length > 0) {\n if (roles.length === 1) {\n conditions.push(`hasRole('${roles[0]}')`);\n } else {\n conditions.push(`hasAnyRole(${JSON.stringify(roles)})`);\n }\n } else {\n conditions.push(\"isAuthenticated()\");\n }\n break;\n\n case \"reauth\":\n conditions.push(\"isAuthenticated()\");\n conditions.push(\"isRecentAuth()\");\n break;\n }\n\n // 소유자 기반 접근 제어\n if (ownerField && (operation === \"read\" || operation === \"update\" || operation === \"delete\")) {\n const ownerCondition = operation === \"read\"\n ? `resource.data.${ownerField} == request.auth.uid`\n : `resource.data.${ownerField} == request.auth.uid`;\n\n if (conditions.length > 0) {\n // 소유자이거나 권한이 있는 경우\n return `(${conditions.join(\" && \")}) || (isAuthenticated() && ${ownerCondition})`;\n }\n return `isAuthenticated() && ${ownerCondition}`;\n }\n\n if (ownerField && operation === \"create\") {\n conditions.push(`request.resource.data.${ownerField} == request.auth.uid`);\n }\n\n return conditions.join(\" && \") || \"false\";\n}\n\n/**\n * 모델 정의에서 Firestore 인덱스 설정 생성 (firestore.indexes.json 형식)\n */\nexport function generateFirestoreIndexes(\n models: Record<string, ModelDefinition>,\n): { indexes: Array<{ collectionGroup: string; queryScope: string; fields: Array<{ fieldPath: string; order?: string }> }> } {\n const indexes: Array<{\n collectionGroup: string;\n queryScope: string;\n fields: Array<{ fieldPath: string; order?: string }>;\n }> = [];\n\n for (const model of Object.values(models)) {\n if (model.indexes) {\n for (const index of model.indexes) {\n indexes.push({\n collectionGroup: model.collection,\n queryScope: \"COLLECTION\",\n fields: index.fields.map((f) => ({\n fieldPath: f.field,\n order: (f.order || \"asc\").toUpperCase(),\n })),\n });\n }\n }\n }\n\n return { indexes };\n}\n","/**\n * Clawfire Firebase Hosting & firebase.json Config Generator\n */\nimport type { ClawfireConfig } from \"../core/config.js\";\n\nexport interface FirebaseJsonConfig {\n hosting?: {\n public: string;\n ignore: string[];\n rewrites?: Array<{ source: string; function?: string; destination?: string }>;\n headers?: Array<{ source: string; headers: Array<{ key: string; value: string }> }>;\n };\n functions?: {\n source: string;\n runtime?: string;\n codebase?: string;\n } | Array<{\n source: string;\n codebase: string;\n runtime?: string;\n }>;\n firestore?: {\n rules: string;\n indexes: string;\n };\n emulators?: {\n functions?: { port: number };\n firestore?: { port: number };\n auth?: { port: number };\n hosting?: { port: number };\n ui?: { enabled: boolean; port?: number };\n };\n}\n\n/**\n * firebase.json 설정 생성\n */\nexport function generateFirebaseJson(config?: Partial<ClawfireConfig>): FirebaseJsonConfig {\n return {\n hosting: {\n public: \"public\",\n ignore: [\"firebase.json\", \"**/.*\", \"**/node_modules/**\"],\n rewrites: [\n {\n source: \"/api/**\",\n function: \"api\",\n },\n {\n source: \"**\",\n destination: \"/index.html\",\n },\n ],\n headers: [\n {\n source: \"/api/**\",\n headers: [\n { key: \"Cache-Control\", value: \"no-store\" },\n { key: \"X-Content-Type-Options\", value: \"nosniff\" },\n { key: \"X-Frame-Options\", value: \"DENY\" },\n { key: \"X-XSS-Protection\", value: \"1; mode=block\" },\n { key: \"Referrer-Policy\", value: \"strict-origin-when-cross-origin\" },\n ],\n },\n {\n source: \"**/*.@(js|css|svg|png|jpg|jpeg|gif|ico|webp|woff|woff2)\",\n headers: [\n { key: \"Cache-Control\", value: \"public, max-age=31536000, immutable\" },\n ],\n },\n ],\n },\n functions: {\n source: \"functions\",\n runtime: \"nodejs20\",\n codebase: \"clawfire\",\n },\n firestore: {\n rules: \"firestore.rules\",\n indexes: \"firestore.indexes.json\",\n },\n emulators: {\n functions: { port: 5001 },\n firestore: { port: 8080 },\n auth: { port: 9099 },\n hosting: { port: 5000 },\n ui: { enabled: true, port: 4000 },\n },\n };\n}\n\n/**\n * 기본 Firestore rules 파일 내용 생성\n */\nexport function generateDefaultRules(): string {\n return `rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n\n // Helper functions\n function isAuthenticated() {\n return request.auth != null;\n }\n\n function isOwner(userId) {\n return isAuthenticated() && request.auth.uid == userId;\n }\n\n function hasRole(role) {\n return isAuthenticated() && request.auth.token.role == role;\n }\n\n // Default: deny all\n match /{document=**} {\n allow read, write: if false;\n }\n }\n}\n`;\n}\n","/**\n * Clawfire Auth Helpers\n *\n * Firebase Auth 통합: 토큰 검증, 역할 관리, 재인증\n */\nimport type { AuthContext, AuthLevel } from \"../core/schema.js\";\nimport { Errors } from \"../core/errors.js\";\n\n// ─── Server-side Auth (Admin SDK) ────────────────────────────────────\n\n/**\n * Firebase ID 토큰에서 AuthContext 추출 (서버사이드)\n */\nexport async function verifyToken(\n auth: any, // firebase-admin Auth instance\n idToken: string,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken);\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 재인증 토큰 검증 (최근 5분 이내 인증)\n */\nexport async function verifyReauth(\n auth: any,\n idToken: string,\n maxAgeSeconds = 300,\n): Promise<AuthContext> {\n const decoded = await auth.verifyIdToken(idToken, true);\n const authTime = decoded.auth_time * 1000;\n const now = Date.now();\n\n if (now - authTime > maxAgeSeconds * 1000) {\n throw Errors.reauthRequired(\n `Re-authentication required. Last auth was ${Math.floor((now - authTime) / 1000)}s ago.`,\n );\n }\n\n return {\n uid: decoded.uid,\n email: decoded.email,\n emailVerified: decoded.email_verified,\n role: decoded.role || decoded.customClaims?.role,\n customClaims: decoded,\n token: idToken,\n };\n}\n\n/**\n * 요청에서 Authorization 헤더의 Bearer 토큰 추출\n */\nexport function extractBearerToken(authHeader?: string): string | null {\n if (!authHeader) return null;\n const parts = authHeader.split(\" \");\n if (parts.length !== 2 || parts[0] !== \"Bearer\") return null;\n return parts[1];\n}\n\n/**\n * 인증 수준 체크\n */\nexport function checkAuthLevel(\n authCtx: AuthContext | null,\n level: AuthLevel,\n roles?: string[],\n reauthenticated?: boolean,\n): void {\n switch (level) {\n case \"public\":\n return; // 누구나 접근 가능\n\n case \"authenticated\":\n if (!authCtx) throw Errors.unauthorized();\n return;\n\n case \"role\":\n if (!authCtx) throw Errors.unauthorized();\n if (!roles || roles.length === 0) return;\n if (!authCtx.role || !roles.includes(authCtx.role)) {\n throw Errors.forbidden(`Required role: ${roles.join(\" or \")}`);\n }\n return;\n\n case \"reauth\":\n if (!authCtx) throw Errors.unauthorized();\n if (!reauthenticated) {\n throw Errors.reauthRequired();\n }\n return;\n }\n}\n\n// ─── Custom Claims / Role Management ─────────────────────────────────\n\n/**\n * 사용자에게 역할(Role) 설정\n */\nexport async function setUserRole(\n auth: any,\n uid: string,\n role: string,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, role });\n}\n\n/**\n * 사용자의 역할 조회\n */\nexport async function getUserRole(auth: any, uid: string): Promise<string | undefined> {\n const user = await auth.getUser(uid);\n return user.customClaims?.role;\n}\n\n/**\n * 사용자에게 커스텀 클레임 설정\n */\nexport async function setCustomClaims(\n auth: any,\n uid: string,\n claims: Record<string, unknown>,\n): Promise<void> {\n const user = await auth.getUser(uid);\n const currentClaims = user.customClaims || {};\n await auth.setCustomUserClaims(uid, { ...currentClaims, ...claims });\n}\n\n// ─── Client-side Auth Helpers ────────────────────────────────────────\n\n/**\n * 클라이언트 사이드 Auth 헬퍼 (firebase/auth 래핑)\n */\nexport function createClientAuth(firebaseAuth: any) {\n return {\n /** 현재 사용자 조회 */\n getCurrentUser(): any | null {\n return firebaseAuth.currentUser;\n },\n\n /** ID 토큰 취득 */\n async getIdToken(forceRefresh = false): Promise<string | null> {\n const user = firebaseAuth.currentUser;\n if (!user) return null;\n return user.getIdToken(forceRefresh);\n },\n\n /** 인증 상태 변경 리스너 */\n onAuthStateChanged(callback: (user: any | null) => void): () => void {\n return firebaseAuth.onAuthStateChanged(callback);\n },\n\n /** 로그아웃 */\n async signOut(): Promise<void> {\n await firebaseAuth.signOut();\n },\n\n /** 원시 auth 접근 */\n raw: firebaseAuth,\n };\n}\n\nexport type ClientAuth = ReturnType<typeof createClientAuth>;\n","/**\n * Clawfire Schema & Contract System\n *\n * 모든 API는 input/output schema + meta + handler로 구성된 \"계약(Contract)\"으로 정의됩니다.\n * Zod를 사용하여 타입 안전성과 런타임 검증을 동시에 보장합니다.\n */\nimport { z, type ZodType, type ZodObject, type ZodRawShape } from \"zod\";\n\n// ─── Types ───────────────────────────────────────────────────────────\n\n/** 인증 컨텍스트 */\nexport interface AuthContext {\n uid: string;\n email?: string;\n emailVerified?: boolean;\n role?: string;\n customClaims?: Record<string, unknown>;\n token?: string;\n}\n\n/** API 핸들러에 전달되는 컨텍스트 */\nexport interface HandlerContext {\n auth: AuthContext | null;\n /** 재인증 여부 (민감 작업용) */\n reauthenticated?: boolean;\n /** 원본 요청 헤더 */\n headers?: Record<string, string>;\n /** 요청 IP */\n ip?: string;\n}\n\n/** 권한 수준 */\nexport type AuthLevel = \"public\" | \"authenticated\" | \"role\" | \"reauth\";\n\n/** API 메타데이터 */\nexport interface APIMeta {\n /** API 설명 (AI/Playground용) */\n description: string;\n /** 태그 (그룹화용) */\n tags?: string[];\n /** 인증 요구 수준 */\n auth?: AuthLevel;\n /** 필요 역할 (auth가 'role'일 때) */\n roles?: string[];\n /** 재인증 필요 여부 */\n reauth?: boolean;\n /** Rate limit (초당 요청 수) */\n rateLimit?: number;\n /** 비활성화 여부 */\n deprecated?: boolean;\n /** 예시 입력값 */\n exampleInput?: unknown;\n /** 예시 출력값 */\n exampleOutput?: unknown;\n}\n\n/** API 계약 정의 */\nexport interface APIContract<\n TInput extends ZodType = ZodType,\n TOutput extends ZodType = ZodType,\n> {\n /** 입력 스키마 */\n input: TInput;\n /** 출력 스키마 */\n output: TOutput;\n /** 메타데이터 */\n meta: APIMeta;\n /** 핸들러 함수 */\n handler: (\n input: z.infer<TInput>,\n ctx: HandlerContext,\n ) => Promise<z.infer<TOutput>>;\n}\n\n/** 모델 필드 정의 */\nexport interface ModelField {\n type: \"string\" | \"number\" | \"boolean\" | \"timestamp\" | \"array\" | \"map\" | \"reference\" | \"geopoint\";\n required?: boolean;\n description?: string;\n default?: unknown;\n /** 배열 아이템 타입 */\n items?: ModelField;\n /** 맵 값 타입 */\n values?: ModelField;\n /** 참조 대상 컬렉션 */\n ref?: string;\n /** enum 값 리스트 */\n enum?: string[];\n}\n\n/** 모델 정의 */\nexport interface ModelDefinition {\n /** 컬렉션 이름 */\n collection: string;\n /** 필드 정의 */\n fields: Record<string, ModelField>;\n /** 서브컬렉션 */\n subcollections?: Record<string, ModelDefinition>;\n /** 인덱스 */\n indexes?: ModelIndex[];\n /** 보안 규칙 */\n rules?: ModelRules;\n /** 타임스탬프 자동 생성 */\n timestamps?: boolean;\n /** 소프트 삭제 */\n softDelete?: boolean;\n}\n\n/** Firestore 인덱스 */\nexport interface ModelIndex {\n fields: Array<{ field: string; order?: \"asc\" | \"desc\" }>;\n}\n\n/** 모델 보안 규칙 */\nexport interface ModelRules {\n read?: AuthLevel;\n create?: AuthLevel;\n update?: AuthLevel;\n delete?: AuthLevel;\n readRoles?: string[];\n createRoles?: string[];\n updateRoles?: string[];\n deleteRoles?: string[];\n /** 소유자만 읽기/쓰기 가능 필드 */\n ownerField?: string;\n}\n\n// ─── Builders ────────────────────────────────────────────────────────\n\n/**\n * API 계약 정의\n *\n * @example\n * ```ts\n * export default defineAPI({\n * input: z.object({ name: z.string() }),\n * output: z.object({ id: z.string(), name: z.string() }),\n * meta: { description: \"상품 생성\", auth: \"authenticated\" },\n * handler: async (input, ctx) => {\n * const id = await db.create(\"products\", input);\n * return { id, ...input };\n * }\n * });\n * ```\n */\nexport function defineAPI<\n TInput extends ZodType,\n TOutput extends ZodType,\n>(contract: APIContract<TInput, TOutput>): APIContract<TInput, TOutput> {\n return contract;\n}\n\n/**\n * 모델(Firestore 컬렉션) 정의\n *\n * @example\n * ```ts\n * export const Product = defineModel({\n * collection: \"products\",\n * fields: {\n * name: { type: \"string\", required: true },\n * price: { type: \"number\", required: true },\n * tags: { type: \"array\", items: { type: \"string\" } },\n * },\n * timestamps: true,\n * rules: { read: \"public\", create: \"authenticated\" }\n * });\n * ```\n */\nexport function defineModel(definition: ModelDefinition): ModelDefinition {\n return {\n timestamps: true,\n ...definition,\n };\n}\n\n// ─── Schema Utilities ────────────────────────────────────────────────\n\n/** Zod 스키마에서 JSON Schema 생성 (Playground/문서용) */\nexport function zodToJsonSchema(schema: ZodType): Record<string, unknown> {\n return extractZodShape(schema);\n}\n\nfunction extractZodShape(schema: ZodType): Record<string, unknown> {\n const def = (schema as any)._def;\n\n if (!def) return { type: \"unknown\" };\n\n switch (def.typeName) {\n case \"ZodObject\": {\n const shape = (schema as ZodObject<ZodRawShape>).shape;\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, value] of Object.entries(shape)) {\n properties[key] = extractZodShape(value as ZodType);\n if (!(value as any).isOptional?.()) {\n const innerDef = (value as any)._def;\n if (innerDef?.typeName !== \"ZodOptional\" && innerDef?.typeName !== \"ZodDefault\") {\n required.push(key);\n }\n }\n }\n\n return { type: \"object\", properties, ...(required.length > 0 ? { required } : {}) };\n }\n case \"ZodString\":\n return { type: \"string\", ...(def.checks?.length ? extractStringChecks(def.checks) : {}) };\n case \"ZodNumber\":\n return { type: \"number\" };\n case \"ZodBoolean\":\n return { type: \"boolean\" };\n case \"ZodArray\":\n return { type: \"array\", items: extractZodShape(def.type) };\n case \"ZodEnum\":\n return { type: \"string\", enum: def.values };\n case \"ZodOptional\":\n return { ...extractZodShape(def.innerType), optional: true };\n case \"ZodDefault\":\n return { ...extractZodShape(def.innerType), default: def.defaultValue() };\n case \"ZodNullable\":\n return { ...extractZodShape(def.innerType), nullable: true };\n case \"ZodLiteral\":\n return { type: typeof def.value, const: def.value };\n case \"ZodUnion\":\n return { oneOf: def.options.map((o: ZodType) => extractZodShape(o)) };\n case \"ZodRecord\":\n return { type: \"object\", additionalProperties: extractZodShape(def.valueType) };\n case \"ZodDate\":\n return { type: \"string\", format: \"date-time\" };\n default:\n return { type: \"unknown\" };\n }\n}\n\nfunction extractStringChecks(checks: Array<{ kind: string; value?: unknown }>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const check of checks) {\n switch (check.kind) {\n case \"min\": result.minLength = check.value; break;\n case \"max\": result.maxLength = check.value; break;\n case \"email\": result.format = \"email\"; break;\n case \"url\": result.format = \"uri\"; break;\n case \"uuid\": result.format = \"uuid\"; break;\n }\n }\n return result;\n}\n\n/** 모델 정의에서 Zod 스키마 자동 생성 */\nexport function modelToZodSchema(model: ModelDefinition): ZodObject<ZodRawShape> {\n const shape: ZodRawShape = {};\n\n for (const [key, field] of Object.entries(model.fields)) {\n let fieldSchema: ZodType = fieldToZod(field);\n if (!field.required) {\n fieldSchema = fieldSchema.optional();\n }\n shape[key] = fieldSchema;\n }\n\n if (model.timestamps) {\n shape.createdAt = z.string().datetime().optional();\n shape.updatedAt = z.string().datetime().optional();\n }\n\n if (model.softDelete) {\n shape.deletedAt = z.string().datetime().nullable().optional();\n }\n\n return z.object(shape);\n}\n\nfunction fieldToZod(field: ModelField): ZodType {\n switch (field.type) {\n case \"string\":\n if (field.enum) return z.enum(field.enum as [string, ...string[]]);\n return z.string();\n case \"number\":\n return z.number();\n case \"boolean\":\n return z.boolean();\n case \"timestamp\":\n return z.string().datetime();\n case \"array\":\n if (field.items) return z.array(fieldToZod(field.items));\n return z.array(z.unknown());\n case \"map\":\n if (field.values) return z.record(z.string(), fieldToZod(field.values));\n return z.record(z.string(), z.unknown());\n case \"reference\":\n return z.string(); // 참조는 문서 경로 문자열\n case \"geopoint\":\n return z.object({ latitude: z.number(), longitude: z.number() });\n default:\n return z.unknown();\n }\n}\n\n// ─── Manifest ────────────────────────────────────────────────────────\n\n/** API 매니페스트 항목 */\nexport interface ManifestEntry {\n path: string;\n method: \"POST\"; // Clawfire는 모두 POST\n meta: APIMeta;\n inputSchema: Record<string, unknown>;\n outputSchema: Record<string, unknown>;\n}\n\n/** 전체 매니페스트 */\nexport interface Manifest {\n version: string;\n generatedAt: string;\n apis: ManifestEntry[];\n models: Record<string, ModelDefinition>;\n}\n\n/** 계약에서 매니페스트 항목 생성 */\nexport function contractToManifest(\n path: string,\n contract: APIContract,\n): ManifestEntry {\n return {\n path,\n method: \"POST\",\n meta: contract.meta,\n inputSchema: zodToJsonSchema(contract.input),\n outputSchema: zodToJsonSchema(contract.output),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,93 @@
1
+ import { e as ModelDefinition } from './schema-BJsictSV.cjs';
2
+ export { f as ModelField, i as ModelIndex, j as ModelRules, h as defineModel, m as modelToZodSchema } from './schema-BJsictSV.cjs';
3
+ import { C as ClawfireConfig } from './config-QMBJRn9G.cjs';
4
+ export { g as getUserRole, s as setCustomClaims, b as setUserRole } from './auth-DQ3cifhb.cjs';
5
+ import 'zod';
6
+
7
+ /**
8
+ * Clawfire Security Rules Generator
9
+ *
10
+ * 모델 정의에서 Firestore Security Rules 자동 생성
11
+ */
12
+
13
+ /**
14
+ * 모델 정의 배열에서 Firestore Security Rules 생성
15
+ */
16
+ declare function generateFirestoreRules(models: Record<string, ModelDefinition>): string;
17
+ /**
18
+ * 모델 정의에서 Firestore 인덱스 설정 생성 (firestore.indexes.json 형식)
19
+ */
20
+ declare function generateFirestoreIndexes(models: Record<string, ModelDefinition>): {
21
+ indexes: Array<{
22
+ collectionGroup: string;
23
+ queryScope: string;
24
+ fields: Array<{
25
+ fieldPath: string;
26
+ order?: string;
27
+ }>;
28
+ }>;
29
+ };
30
+
31
+ /**
32
+ * Clawfire Firebase Hosting & firebase.json Config Generator
33
+ */
34
+
35
+ interface FirebaseJsonConfig {
36
+ hosting?: {
37
+ public: string;
38
+ ignore: string[];
39
+ rewrites?: Array<{
40
+ source: string;
41
+ function?: string;
42
+ destination?: string;
43
+ }>;
44
+ headers?: Array<{
45
+ source: string;
46
+ headers: Array<{
47
+ key: string;
48
+ value: string;
49
+ }>;
50
+ }>;
51
+ };
52
+ functions?: {
53
+ source: string;
54
+ runtime?: string;
55
+ codebase?: string;
56
+ } | Array<{
57
+ source: string;
58
+ codebase: string;
59
+ runtime?: string;
60
+ }>;
61
+ firestore?: {
62
+ rules: string;
63
+ indexes: string;
64
+ };
65
+ emulators?: {
66
+ functions?: {
67
+ port: number;
68
+ };
69
+ firestore?: {
70
+ port: number;
71
+ };
72
+ auth?: {
73
+ port: number;
74
+ };
75
+ hosting?: {
76
+ port: number;
77
+ };
78
+ ui?: {
79
+ enabled: boolean;
80
+ port?: number;
81
+ };
82
+ };
83
+ }
84
+ /**
85
+ * firebase.json 설정 생성
86
+ */
87
+ declare function generateFirebaseJson(config?: Partial<ClawfireConfig>): FirebaseJsonConfig;
88
+ /**
89
+ * 기본 Firestore rules 파일 내용 생성
90
+ */
91
+ declare function generateDefaultRules(): string;
92
+
93
+ export { type FirebaseJsonConfig, ModelDefinition, generateDefaultRules, generateFirebaseJson, generateFirestoreIndexes, generateFirestoreRules };