logo
מדד

עולם ההרחבות — מדריך ארכיטקטורה מלא

מסמך זה מפרט את כל המערכת סביב הרחבות בפלטפורמת מדד — מהשלב שבו מפתח כותב קוד ועד שמשתמש קצה מפעיל הרחבה בדשבורד.


תוכן עניינים

  1. סקירה כללית
  2. מבנה חובה של הרחבה
  3. סביבת הריצה (Runtime)
  4. Bridge API — תקשורת הרחבה ↔ פלטפורמה
  5. הרשאות (Permissions)
  6. אחסון מפתח-ערך (Store)
  7. אחסון ישויות (Entity Storage)
  8. גישה, רישוי ומגבלות
  9. שליחת מייל (SEND_EMAIL)
  10. תזמון משימות (Scheduler)
  11. קריאה ל-AI (AI_COMPLETE)
  12. קריאות HTTP חיצוניות (FETCH_EXTERNAL)
  13. הדפסה, PDF והורדת קבצים
  14. הגדרת Config ו-Extension Data
  15. הצהרות @extension-entities ו-@extension-config
  16. מחזור חיי פיתוח — מפתח → חנות → משתמש
  17. חנות ההרחבות וקטגוריות
  18. פרומפט הפלטפורמה (Platform Prompt)
  19. מודלי בסיס נתונים — מפת Prisma

1. סקירה כללית

הרחבה (Extension) היא קובץ JavaScript יחיד (bundle) שרץ בתוך iframe מבודד. ההרחבה מתקשרת עם הפלטפורמה אך ורק דרך ה-Bridge API — ממשק הודעות מבוסס postMessage.

┌─────────────────────────────────────────────────┐
│  פלטפורמת מדד (Parent)                          │
│                                                   │
│  ┌─────────────────────────────────────────────┐ │
│  │  iframe (sandbox)                            │ │
│  │                                               │ │
│  │  window.MyExtension = {                       │ │
│  │    metadata: { ... },                         │ │
│  │    mount(containerId, bridge) { ... },        │ │
│  │    unmount() { ... }                          │ │
│  │  }                                            │ │
│  │                                               │ │
│  │  bridge.send({ type: "STORE_GET", ... })  ───┼─┤──► API route
│  │  bridge.on("STORE_GET_RESPONSE", fn)      ◄──┼─┤──◄ Response
│  └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘

עקרונות ארכיטקטוניים:

  • בידוד מלא — ההרחבה רצה ב-iframe עם sandbox attributes. אין גישה ישירה ל-DOM, cookies, או localStorage של הפלטפורמה.
  • הרשאות מפורשות — כל פקודה דורשת הצהרה ב-metadata.permissions.
  • ריבוי דיירים — הנתונים מבודדים לפי extensionSlug + companyId. הרחבה אחת לא רואה נתונים של חברה אחרת.
  • RTL תמיד — הפלטפורמה מיועדת לשוק ישראלי-ערבי. כל ה-UI הוא RTL.

2. מבנה חובה של הרחבה

כל bundle חייב להגדיר window.MyExtension עם שלושה שדות:

/* @extension-entities
[
  { "type": "contact", "label": "איש קשר", "description": "...", "recommendLimit": true }
]
*/

/* @extension-config
[
  { "key": "maxItems", "type": "number", "default": 10, "label": "מקסימום פריטים" }
]
*/

window.MyExtension = {
  metadata: {
    name: "שם ההרחבה",
    version: "1.0.0",
    permissions: ["GET_CURRENT_USER", "STORE_GET", "STORE_SET"],
    // אופציונלי:
    locales: ["he", "ar"],
    defaultLocale: "he",
    dependencies: [{ slug: "other-ext", version: "1.0.0" }],
  },

  mount(containerId, bridge) {
    var root = document.getElementById(containerId);
    // ... בניית ה-UI
  },

  unmount() {
    // ניקוי משאבים
  },
};
שדהחובהתיאור
metadata.nameשם ההרחבה
metadata.versionגרסה (semver)
metadata.permissionsמערך הרשאות Bridge API
metadata.localesשפות נתמכות (אם רב-לשוני)
metadata.defaultLocaleשפת ברירת מחדל
metadata.dependenciesתלויות בהרחבות אחרות
mount(containerId, bridge)פונקציית הרצה — containerId תמיד "ext-container"
unmount()פונקציית ניקוי

כללים:

  • אסור import/export — הקובץ הוא bundle עצמאי.
  • אסור (function(){ ... })() — חובה window.MyExtension = { ... } ישירות.
  • אסור localStorage / document.cookie — השתמש ב-STORE_* דרך Bridge.
  • אסור fetch ישיר — השתמש ב-FETCH_EXTERNAL דרך Bridge.

3. סביבת הריצה (Runtime)

קבצים ראשיים

קובץתפקיד
src/app/sandbox-frame/page.tsxעמוד ה-iframe — טוען ומריץ את ה-bundle
src/app/dashboard/extensions/[slug]/page.tsxRunner ייצור — צד ה-parent
src/app/dashboard/extensions/beta/[assignmentId]/page.tsxRunner בטא
src/app/admin/sandbox/page.tsxSandbox אדמין
src/app/dashboard/developer/sandbox/page.tsxSandbox מפתח

זרימת הטעינה

1. משתמש נכנס ל-/dashboard/extensions/[slug]
   │
2. Parent קורא /api/my-extensions/[slug]
   │ → בדיקת גישה, ספירת שימוש, טעינת config
   │
3. Parent מרנדר iframe ל-/sandbox-frame?url=<bundleUrl>&theme=<theme>
   │ sandbox="allow-scripts allow-forms allow-same-origin allow-modals allow-popups allow-downloads"
   │
4. sandbox-frame מריץ fetch(bundleUrl) → new Function(code)()
   │ → Watchdog 5 שניות: אם window.MyExtension לא נמצא — timeout
   │
5. נבנה אובייקט bridge:
   │ • bridge.send(msg) → window.parent.postMessage(msg, "*")
   │ • bridge.on(type, cb) → רישום listener
   │
6. ext.mount("ext-container", bridge)
   │
7. הודעות Bridge נשלחות/מתקבלות דרך postMessage

ערכות נושא (Theme)

ההרחבה מקבלת Theme דרך query param:

var theme = new URL(location.href).searchParams.get("theme"); // "light" | "dark" | "black"

4. Bridge API — תקשורת הרחבה ↔ פלטפורמה

כל התקשורת עוברת דרך bridge.send() (שליחה) ו-bridge.on() (האזנה לתגובה).

טבלת פקודות מלאה

פקודה (type)פרמטריםתגובה (_RESPONSE)שדות בתגובהAPI Route
SHOW_TOASTtext, kind (success/error/info)צד לקוח
NAVIGATEto (נתיב /dashboard/...)צד לקוח
OPEN_NEW_WINDOWurl (http/https בלבד)OPEN_NEW_WINDOW_RESPONSEok / errorצד לקוח
GET_CURRENT_USERGET_CURRENT_USER_RESPONSEid, name, email, company, companyIdמה-session
GET_COMPANY_USERSGET_COMPANY_USERS_RESPONSEcompanyId, companyName, users[] (id, name, email, role)/api/extension-bridge/company-users
GET_MY_EXTENSION_CONFIGGET_MY_EXTENSION_CONFIG_RESPONSEconfigמנתונים טעונים
GET_EXTENSION_DATAslug, version?GET_EXTENSION_DATA_RESPONSEdata/api/developer/extension-data
GET_ACCESS_STATUSGET_ACCESS_STATUS_RESPONSEstatus, isActive, isFrozen, isExpired, usageCount, usageLimit, entityCount, entityLimit, limitMode, limitEntityType, lastUsedAt, grantedAt, expiresAt/api/extension-bridge/access-status
STORE_SETkey, valueSTORE_SET_RESPONSEok/api/extension-bridge/store
STORE_GETkeySTORE_GET_RESPONSEkey, value/api/extension-bridge/store
STORE_DELETEkeySTORE_DELETE_RESPONSEok/api/extension-bridge/store
STORE_LISTprefixSTORE_LIST_RESPONSEkeys/api/extension-bridge/store
ENTITY_CREATEentityType, dataENTITY_CREATE_RESPONSEok, entity / error/api/extension-bridge/entity
ENTITY_LISTentityType, filter?ENTITY_LIST_RESPONSEok, entities[], total/api/extension-bridge/entity
ENTITY_UPDATEentityType, entityId, dataENTITY_UPDATE_RESPONSEok, entity / error/api/extension-bridge/entity
ENTITY_DELETEentityType, entityIdENTITY_DELETE_RESPONSEok / error/api/extension-bridge/entity
ENTITY_COUNTentityTypeENTITY_COUNT_RESPONSEok, count/api/extension-bridge/entity
FETCH_EXTERNALurl, method?, headers?, body?FETCH_EXTERNAL_RESPONSEstatus, data / error/api/extension-bridge/fetch
SEND_EMAILto, subject, html?, text?, replyTo?SEND_EMAIL_RESPONSEok, sent, dailySent, dailyLimit / error/api/extension-bridge/send-email
SCHEDULE_CREATEactionType, triggerAt, payload, ref?SCHEDULE_CREATE_RESPONSEok, scheduleId / error/api/extension-bridge/scheduler
SCHEDULE_LISTSCHEDULE_LIST_RESPONSEok, items[]/api/extension-bridge/scheduler
SCHEDULE_DELETEscheduleIdSCHEDULE_DELETE_RESPONSEok / error/api/extension-bridge/scheduler
AI_COMPLETEprompt, maxTokens?AI_COMPLETE_RESPONSEok, text / error/api/extension-bridge/ai
PRINT_HTMLhtmlPRINT_HTML_RESPONSEok / errorצד לקוח
GENERATE_PDFhtmlGENERATE_PDF_RESPONSEok, info / errorצד לקוח
DOWNLOAD_FILEfilename, content, mimeTypeDOWNLOAD_FILE_RESPONSEok / errorצד לקוח
REPORT_USAGEamount? (ברירת מחדל: 1)REPORT_USAGE_RESULTok, usageCount, usageLimit / error/api/extensions/[slug]/report-usage

תבנית שימוש בסיסית

// שלב 1: רשום listener לתגובה
bridge.on("STORE_GET_RESPONSE", function(res) {
  if (res.error) { console.error(res.error); return; }
  console.log(res.key, res.value);
});

// שלב 2: שלח פקודה
bridge.send({ type: "STORE_GET", key: "myData" });

5. הרשאות (Permissions)

קובץ מרכזי: src/lib/extensionPermissions.ts

כל הרחבה מצהירה אילו פקודות Bridge היא צריכה ב-metadata.permissions. פקודות שלא הוצהרו ייחסמו.

הרשאהתיאורפנימית?
GET_CURRENT_USERקרא מידע מזוהה על המשתמש
GET_COMPANY_USERSרשימת משתמשים בחברה + תפקידים
GET_MY_EXTENSION_CONFIGהגדרות config שמורות
GET_EXTENSION_DATAנתונים מ-contractJson
SHOW_TOASTהודעות Toast
NAVIGATEניווט בתוך /dashboard
STORE_SETשמירת ערך
STORE_GETקריאת ערך
STORE_DELETEמחיקת ערך
STORE_LISTרשימת מפתחות
FETCH_EXTERNALקריאת HTTP לשרת חיצוני
PRINT_HTMLהדפסת HTML
GENERATE_PDFהפקת PDF מ-HTML
OPEN_NEW_WINDOWפתיחת URL בטאב חדש
DOWNLOAD_FILEהורדת קובץ
REPORT_USAGEספירת כניסות (אוטומטי)
ENTITY_CREATEיצירת ישות
ENTITY_LISTרשימת ישויות
ENTITY_UPDATEעדכון ישות
ENTITY_DELETEמחיקת ישות
ENTITY_COUNTספירת ישויות
GET_ACCESS_STATUSבדיקת סטטוס גישה
SEND_EMAILשליחת מייל דרך SMTP
AI_COMPLETEקריאה למודל AI
SCHEDULE_CREATEיצירת משימה מתוזמנת
SCHEDULE_LISTרשימת משימות מתוזמנות
SCHEDULE_DELETEמחיקת משימה מתוזמנת

טיפוס TypeScript: PermissionId — union של כל מזהי ההרשאות.


6. אחסון מפתח-ערך (Store)

מודל DB: ExtensionData
API Route: src/app/api/extension-bridge/store/route.ts

מודל Prisma

model ExtensionData {
  id            String   @id @default(cuid())
  extensionSlug String
  companyId     String
  key           String
  value         Json
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt

  @@unique([extensionSlug, companyId, key])
  @@index([extensionSlug, companyId])
}

פעולות

פעולהפקודהתיאור
כתיבהSTORE_SETupsert לפי slug + companyId + key
קריאהSTORE_GETlookup — מחזיר value או null
מחיקהSTORE_DELETEמחיקה שקטה (לא נכשל אם לא קיים)
רשימהSTORE_LISTfindMany עם אופציה ל-prefix

בידוד נתונים

  • כל הנתונים מבודדים לפי extensionSlug + companyId.
  • Sandbox משתמש ב-"__sandbox__" כ-companyId.
  • ערך נשמר כ-JSON (Prisma Json type) — תומך בכל ערך serializable.

מתי להשתמש ב-Store (ולא Entity)

  • הגדרות, מצב (state), cache, ערכים פשוטים.
  • אין אכיפת מגבלות מצד השרת — ההרחבה אחראית להגביל אוספים בעצמה.

7. אחסון ישויות (Entity Storage)

מודל DB: ExtensionEntity
API Route: src/app/api/extension-bridge/entity/route.ts

מודל Prisma

model ExtensionEntity {
  id            String   @id @default(cuid())
  extensionSlug String
  companyId     String
  entityType    String
  data          Json
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt

  @@index([extensionSlug, companyId, entityType])
}

פעולות

פעולהפקודההערות
יצירהENTITY_CREATEאוכף מגבלות (frozen/expired/limit)
רשימהENTITY_LISTתומך ב-filter על שדות ב-data (in-memory)
עדכוןENTITY_UPDATEמחליף את כל ה-data
מחיקהENTITY_DELETEבודק בעלות (slug + companyId)
ספירהENTITY_COUNTספירה פשוטה

אכיפת מגבלות (ב-CREATE בלבד)

כש-limitMode === "entity" וה-entityType מתאים ל-limitEntityType של ההרחבה:

  1. בודק שהגישה פעילה (לא frozen, לא expired)
  2. סופר ישויות קיימות מאותו סוג
  3. משווה מול entityLimitOverride ?? extension.limitValue
  4. מחזיר LIMIT_REACHED אם חורג

מתי להשתמש ב-Entity (ולא Store)

  • רשימות/אוספים: אנשי קשר, חשבוניות, משימות
  • כשצריך ספירה אוטומטית ואכיפת מגבלות מצד השרת
  • כשצריך סינון על שדות

השוואה — Store vs Entity

תכונהStoreEntity
מבנהkey-value חופשירשומות עם id, entityType, data
ספירה מובניתENTITY_COUNT
אכיפת מגבלות✅ בזמן ENTITY_CREATE
חיפוש/סינון❌ (רק prefix ב-LIST)✅ filter על שדות
שימוש מומלץהגדרות, state, cacheרשימות/אוספים

8. גישה, רישוי ומגבלות

מודל Prisma — ExtensionAccess

model ExtensionAccess {
  id                  String    @id @default(cuid())
  extensionId         String
  userId              String?           // הענקה ברמת משתמש
  companyId           String?           // הענקה ברמת חברה
  source              String    @default("free_auto")
  isActive            Boolean   @default(true)
  usageCount          Int       @default(0)
  usageLimit          Int?              // override לכל הענקה
  grantedAt           DateTime  @default(now())
  expiresAt           DateTime?
  lastUsedAt          DateTime?
  frozenAt            DateTime?
  frozenReason        String?
  entityLimitOverride Int?

  @@unique([extensionId, userId])
  @@unique([extensionId, companyId])
}

סוגי מקור גישה (source)

sourceתיאור
admin_grantמנהל העניק ידנית
public_autoהרחבה ציבורית — גישה אוטומטית
free_autoהרחבה חינמית — נוצר אוטומטית

היקף רישוי (licenseScope)

ערךתיאור
"company"(ברירת מחדל) גישה לפי חברה — כל עובדי החברה חולקים אותה הענקה
"user"גישה לפי משתמש — כל משתמש צריך הענקה נפרדת

מצבי גישה

מצבתנאי
ACTIVEisActive: true, לא frozen, לא expired
FROZENfrozenAt מוגדר (פעולת אדמין)
EXPIREDexpiresAt < now
INACTIVEisActive: false
NO_ACCESSלא נמצאה הענקת גישה

מצבי הגבלה (limitMode)

limitModeמנגנון ספירהמה נאכף
"usage"אוטומטי — הפלטפורמה סופרת כל כניסה (פתיחת עמוד)כניסה נחסמת כש-usageCount >= usageLimit
"entity"אוטומטי — ספירת ENTITY_CREATEENTITY_CREATE נכשל כש-count >= limit
"none"איןאין מגבלה

מערכת Trial

  • Extension.trialValue + Extension.trialUnit (hours/days/weeks/months/years)
  • בגישה ראשונה — מחושב expiresAt ונשמר בהענקה
  • כשאדמין משנה הגדרות trial — כל ההענקות הקיימות מתאפסות

לוגיקת גישה בזמן ריצה

משתמש נכנס ל-/dashboard/extensions/[slug]
  │
  ├─ licenseScope === "company"?
  │    └─ חפש ExtensionAccess לפי extensionId + companyId
  │
  ├─ licenseScope === "user"?
  │    └─ חפש לפי extensionId + userId → fallback לחברה
  │
  ├─ SUPERADMIN / ADMIN_ASSISTANT?
  │    └─ עוקף הכל — גישה מלאה
  │
  ├─ isPublic === true בלי הענקה?
  │    └─ יוצר הענקה אוטומטית
  │
  └─ limitMode === "usage"?
       └─ מעלה usageCount ב-1 (רענון בתוך 60 שניות לא נספר)

9. שליחת מייל (SEND_EMAIL)

API Route: src/app/api/extension-bridge/send-email/route.ts

מגבלות

פרמטרערך
נמענים לכל שליחהעד 5
מיילים ליוםעד 50 לכל הרחבה+חברה (מתאפס בחצות UTC)
אורך נושאעד 200 תווים
גוף מיילעד 60,000 תווים
FROMקבוע — כתובת SMTP של הפלטפורמה (לא ניתן לשינוי)

זרימה

הרחבה → SEND_EMAIL { to, subject, html/text, replyTo? }
  │
  ├─ ולידציית פרמטרים (מייל, אורכים)
  ├─ בדיקת SMTP מוגדר
  ├─ בדיקת Rate Limit (ספירה ב-Store עם מפתח __email_daily_count__YYYY-MM-DD)
  ├─ שליחה דרך nodemailer
  └─ עדכון מונה יומי

שגיאות אפשריות

SMTP_NOT_CONFIGURED, DAILY_LIMIT_REACHED, SEND_FAILED, INVALID_PAYLOAD


10. תזמון משימות (Scheduler)

API Route: src/app/api/extension-bridge/scheduler/route.ts
Executor: src/lib/scheduled-task-executor.ts
Cron: src/lib/cron-runner.ts (כל 60 שניות)

מודל Prisma — ScheduledTask

model ScheduledTask {
  id           String    @id @default(cuid())
  extensionId  String
  companyId    String
  userId       String
  action       String              // "SEND_EMAIL"
  payload      Json
  triggerAt    DateTime
  ref          String?             // מפתח ייחודי למניעת כפילויות
  status       String    @default("pending")
  executedAt   DateTime?
  errorMessage String?
  attempts     Int       @default(0)
  createdAt    DateTime  @default(now())

  @@unique([extensionId, companyId, ref])
  @@index([status, triggerAt])
}

מגבלות

קבועערך
MAX_PENDING_PER_EXT100 משימות ממתינות לכל הרחבה+חברה
MAX_ATTEMPTS3 ניסיונות חוזרים לפני כשלון
BATCH_SIZE50 משימות לכל tick
CLEANUP_DAYS30 יום — משימות ישנות נמחקות אוטומטית
טווח triggerAt5 דקות עד שנה קדימה

פעולות נתמכות

כרגע: SEND_EMAIL בלבד.

סטטוסים

statusתיאור
pendingממתינה לביצוע
executedבוצעה בהצלחה
failedנכשלה (אחרי 3 ניסיונות)

זרימת ביצוע

instrumentation.ts
  └─ scheduleCronCheck() → setInterval(tick, 60_000)

tick() (כל 60 שניות):
  ├─ ... (snapshot logic)
  └─ executeScheduledTasks()
       ├─ מצא pending tasks עם triggerAt <= now, attempts < 3
       ├─ לכל משימה: הגדל attempts, בצע action
       │   ├─ SEND_EMAIL → nodemailer
       │   ├─ הצלחה → status = "executed"
       │   └─ כישלון → attempts < 3? נשאר pending : status = "failed"
       └─ ניקוי משימות ישנות (> 30 יום)

שגיאות Bridge

LIMIT_REACHED (100 pending), DUPLICATE_REF (409), NOT_FOUND, CANNOT_DELETE (רק pending ניתנות למחיקה)

דוגמה — תזמון תזכורת מייל

var when = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();

bridge.send({
  type: "SCHEDULE_CREATE",
  actionType: "SEND_EMAIL",
  triggerAt: when,
  ref: "reminder-check-123",
  payload: {
    to: "user@example.com",
    subject: "תזכורת: שיק מספר 123",
    html: "<div dir='rtl'><h2>תזכורת</h2><p>שיק 123 מגיע מחר.</p></div>",
  },
});

11. קריאה ל-AI (AI_COMPLETE)

API Route: src/app/api/extension-bridge/ai/route.ts

תנאים

  • מנהל המערכת חייב להפעיל AI להרחבה דרך ממשק "הרחבות AI"
  • נדרש ANTHROPIC_API_KEY בסביבה

מגבלות

קבועערך
מודלclaude-haiku-4-5-20251001
ברירת מחדל maxTokens1,024
מקסימום מוחלט4,096
cap אפקטיביmin(requested, HARD_MAX, extension.aiMaxTokensPerRequest)

הגדרות אדמין (/api/admin/extensions/[id]/ai-config)

שדהתיאור
aiEnabledהפעלת AI להרחבה
aiMaxTokensPerRequestמגבלת טוקנים לבקשה
aiMonthlyTokenBudgetתקציב טוקנים חודשי

מודל DB — AiUsageLog

model AiUsageLog {
  id               String   @id @default(cuid())
  extensionSlug    String
  companyId        String
  userId           String?
  model            String   @default("claude-haiku-4-5-20251001")
  promptTokens     Int      @default(0)
  completionTokens Int      @default(0)
  totalTokens      Int      @default(0)
  durationMs       Int?
  success          Boolean  @default(true)
  errorCode        String?
  createdAt        DateTime @default(now())
}

שגיאות

AI_NOT_ENABLED, AI_NOT_CONFIGURED, AI_TOKEN_LIMIT_EXCEEDED, AI_BUDGET_EXCEEDED, AI_PROVIDER_ERROR


12. קריאות HTTP חיצוניות (FETCH_EXTERNAL)

API Route: src/app/api/extension-bridge/fetch/route.ts

הגנות אבטחה

הגנהפירוט
SSRFחסימת localhost, טווחי IP פרטיים (10.x, 192.168.x, 172.16-31.x, 127.x, 169.254.x), IPv6 loopback/ULA
פרוטוקולרק http: ו-https:
MethodsGET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Headers חסומיםhost, cookie, authorization, x-forwarded-for
Timeout6 שניות (AbortController)

תגובה

  • אם Content-Type: application/json → JSON
  • אחרת → מחרוזת טקסט

13. הדפסה, PDF והורדת קבצים

כולן מטופלות בצד הלקוח (בלי API call):

PRINT_HTML / GENERATE_PDF

מציגות overlay מסך מלא עם iframe ל-printHtml וכפתור הדפסה.

DOWNLOAD_FILE

bridge.send({ type: "DOWNLOAD_FILE", filename: "report.csv", content: csvContent, mimeType: "text/csv;charset=utf-8" });
  • יוצר Blob מ-content + mimeType
  • לקבצי CSV: מוסיף BOM (\uFEFF) לתמיכה בעברית ב-Excel
  • ריהוט שם קובץ: תווים מסוכנים מוחלפים ב-_, מקסימום 255 תווים

14. הגדרת Config ו-Extension Data

GET_MY_EXTENSION_CONFIG

  1. ההרחבה מצהירה על שדות config ב-@extension-config (ראה סעיף 15)
  2. אדמין מגדיר ערכים דרך ממשק גרפי → נשמר ב-Extension.contractJson.runtimeConfig
  3. בטעינת ההרחבה, extractRuntimeConfig() שולף את ה-config
  4. הרחבה שולחת GET_MY_EXTENSION_CONFIG → מקבלת { config: { ... } }

GET_EXTENSION_DATA

  • זמינה רק למפתחים (תפקיד DEVELOPER)
  • שולפת את ה-contractJson המלא של הרחבה לפי slug
  • שימוש: הרחבה שתלויה בנתוני הרחבה אחרת

15. הצהרות @extension-entities ו-@extension-config

@extension-entities (חובה)

קובץ parser: src/lib/parseExtensionEntities.ts

בלוק הערה בתחילת הקובץ (לפני window.MyExtension) שמצהיר על כל סוגי הישויות:

/* @extension-entities
[
  { "type": "debtor", "label": "חייב", "description": "ישות ליבה", "recommendLimit": true },
  { "type": "payment", "label": "תשלום", "description": "תשלום לחייב", "recommendLimit": false }
]
*/
שדהחובהתיאור
typeמזהה הישות (אנגלית) — חייב להתאים ל-entityType ב-ENTITY_CREATE
labelשם בעברית
descriptionהסבר קצר
recommendLimittrue לישויות ליבה (מומלץ להגביל), false לנגזרות/אוטומטיות
  • גם /* @extension-entities [] */ חוקי אם אין ישויות.
  • ה-Sandbox מציג אזהרה אם הבלוק חסר.
  • אדמין בוחר לפי איזו ישות recommendLimit: true להגביל.

@extension-config (אופציונלי, מומלץ)

קובץ parser: src/lib/parseExtensionConfig.ts

/* @extension-config
[
  { "key": "maxItems", "type": "number",  "default": 10,    "label": "מקסימום פריטים" },
  { "key": "mode",     "type": "string",  "default": "basic","label": "מצב תצוגה" },
  { "key": "enabled",  "type": "boolean", "default": true,   "label": "פעיל" }
]
*/
שדהחובהערכים
keyמחרוזת באנגלית — חייבת להתאים למה שההרחבה קוראת מה-config
type"number" / "string" / "boolean"
defaultערך ברירת מחדל (חייב להתאים לטיפוס)
labelתיאור בעברית
  • בלי הבלוק: אדמין עורך JSON ידנית (עדיין עובד).
  • עם הבלוק: הפלטפורמה בונה ממשק גרפי אוטומטי לעריכת config.

16. מחזור חיי פיתוח — מפתח → חנות → משתמש

קבצים ראשיים

קובץתפקיד
src/app/dashboard/developer/sandbox/page.tsxSandbox מפתח
src/app/dashboard/developer/submit/page.tsxהגשת הרחבה
src/app/dashboard/developer/submissions/page.tsxסטטוס הגשות
src/app/api/developer/submissions/route.tsAPI הגשות
src/app/api/developer/upload-bundle/route.tsהעלאת bundle
src/app/admin/extensions/page.tsxניהול הרחבות (אדמין)

סטטוסי הגשה (SubmissionStatus)

DRAFT ──► PENDING ──┬──► APPROVED ──► IN_STORE
                    ├──► CHANGES_REQUESTED
                    └──► REJECTED

מחזור החיים

1. מפתח כותב bundle
   │
2. בודק ב-Developer Sandbox
   │ (URL או paste code ← LOAD_SCRIPT)
   │
3. מגיש (Submit) → ExtensionSubmission (PENDING)
   │
4. אדמין סוקר:
   │ ├─ APPROVED → ניתן להעביר ל-IN_STORE
   │ ├─ CHANGES_REQUESTED → חוזר למפתח
   │ └─ REJECTED
   │
5. (אופציונלי) בטא:
   │ אדמין מקצה BetaTestAssignment → בודקים רצים ב-/beta/[assignmentId]
   │
6. אדמין יוצר Extension בחנות ומקשר Submission פעיל
   │
7. משתמשים מקבלים גישה:
   │ ├─ isPublic → אוטומטי
   │ ├─ admin_grant → ידני
   │ └─ רכישה (בעתיד)
   │
8. משתמש נכנס → /dashboard/extensions/[slug] → iframe → bridge

תנאי הגשה

  • slug: רק ^[a-z0-9-]+$
  • bundleUrl חייב להסתיים ב-.js
  • חובה: name, slug, shortDescription, description, bundleUrl, version

תפקידי מפתח

  • SystemRole.DEVELOPER — נדרש לגשת לפורטל מפתחים
  • User.developerLicenseExpiry — תוקף רשיון מפתח
  • Company.developerPortalEnabled — הפעלת פורטל מפתחים לחברה

17. חנות ההרחבות וקטגוריות

קבצים

קובץתפקיד
src/app/store/page.tsxעמוד חנות ציבורי
src/app/store/[slug]/page.tsxעמוד פרטי הרחבה
src/components/store/ExtensionStoreView.tsxקומפוננטת תצוגת חנות
src/lib/storeVisibility.tsמדיניות נראות

מודלי DB

model ExtensionCategory {
  id          String   @id @default(cuid())
  slug        String   @unique
  name        String
  description String?
  imageUrl    String?
}

model ExtensionTag {
  id          String   @id @default(cuid())
  slug        String   @unique
  name        String
  description String?
}
  • ההרחבה שומרת category (string) ו-tags (String[]) ישירות על המודל — ללא FK.
  • ExtensionCategory ו-ExtensionTag הם lookup tables לניהול באדמין.

תמחור

שדהערכים
pricingType"paid", "free", "limited"
paymentType"one_time" / null
hasLicenseTiersboolean — האם יש שכבות רישוי
licenseTiersJSON — מבנה שכבות (LicenseTierBuilder)

דירוגים

model ExtensionRating {
  rating      Int
  comment     String?
  userId      String
  extensionId String
  @@unique([userId, extensionId])
}

הסתרת הרחבות

model UserExtensionHidden {
  userId      String
  extensionId String
  @@unique([userId, extensionId])
}

18. פרומפט הפלטפורמה (Platform Prompt)

קובץ מרכזי: src/lib/buildPlatformPrompt.ts (~1,300 שורות)
API Route: src/app/api/developer/platform-prompt/route.ts

תפקיד

מייצר מסמך פרומפט בעברית שמשמש:

  1. מודלי AI — כשמפתחים מבקשים מ-AI לכתוב הרחבה
  2. מפתחים אנושיים — כ-specification מלא

מבנה

  1. טבלת גרסאות פלטפורמה
  2. מבנה חובה של window.MyExtension
  3. טבלת Bridge API — נבנית דינמית מ-ALL_PERMISSIONS דרך permissionsTable()
  4. דוגמאות שימוש לכל פקודה
  5. קודי שגיאה
  6. מגבלת כניסות (usage/entity)
  7. @extension-entities — מפרט + recommendLimit guidelines
  8. @extension-config — מפרט
  9. SEND_EMAIL — מפרט מלא + דוגמאות
  10. AI_COMPLETE — מפרט מלא + דוגמאות
  11. SCHEDULE_* — מפרט מלא + דוגמאות
  12. Entity Storage vs STORE — guidelines
  13. GET_ACCESS_STATUS — מפרט + אינדיקטור שימוש
  14. RoleGuard — מנגנון הרשאות פנימי (ACL)
  15. תמיכה רב-לשונית (he + ar)
  16. פורמט תאריכים ומספרים

פונקציות

  • permissionsTable() — בונה markdown table מ-switch-case על כל permission
  • permissionsEnum() — מחזיר "PERM1" | "PERM2" | ... לטיפוסים
  • buildPlatformPrompt(info: PlatformInfo) — הפונקציה הראשית

19. מודלי בסיס נתונים — מפת Prisma

מודלים הקשורים להרחבות

Extension (הרחבה)
  ├─ ExtensionAccess[]       (הענקות גישה)
  ├─ ExtensionEntity[]       (ישויות - דרך slug)
  ├─ ExtensionData[]         (Store key-value - דרך slug)
  ├─ ExtensionSubmission[]   (הגשות מפתחים)
  ├─ ExtensionRating[]       (דירוגים)
  ├─ ScheduledTask[]         (משימות מתוזמנות)
  ├─ AiUsageLog[]           (לוגי שימוש AI - דרך slug)
  ├─ BetaTestAssignment[]    (דרך Submission)
  └─ DeveloperContract[]     (חוזי מפתחים)

ExtensionCategory (קטגוריות)
ExtensionTag (תגיות)
UserExtensionHidden (הסתרת הרחבות)

שדות עיקריים על Extension

שדהסוגתיאור
slugString (unique)מזהה ייחודי
nameStringשם
bundleUrlString?URL ל-bundle JS
manifestJsonJson?metadata שנשמר
contractJsonJson?נתוני חוזה + runtimeConfig
isPublicBooleanציבורית?
isActiveBooleanפעילה?
licenseScopeString"company" / "user"
limitModeString?"none" / "usage" / "entity"
limitValueInt?ערך מגבלה
limitEntityTypeString?סוג ישות להגבלה
pricingTypeString?"free" / "paid" / "limited"
trialValueInt?ערך trial
trialUnitString?יחידת trial
aiEnabledBooleanAI מופעל?
aiMaxTokensPerRequestInt?מגבלת AI
aiMonthlyTokenBudgetInt?תקציב AI
categoryString?קטגוריה
tagsString[]תגיות
bundleSizeInt?גודל bundle בבתים

נספח — API Routes מסכם

נתיבMethodתיאור
/api/my-extensionsGETרשימת ההרחבות הנגישות למשתמש
/api/my-extensions/[slug]GETטעינת הרחבה (גישה + config + usage)
/api/extension-bridge/storePOSTSTORE_SET / GET / DELETE / LIST
/api/extension-bridge/entityPOSTENTITY_CREATE / LIST / UPDATE / DELETE / COUNT
/api/extension-bridge/fetchPOSTFETCH_EXTERNAL (proxy)
/api/extension-bridge/send-emailPOSTSEND_EMAIL
/api/extension-bridge/schedulerPOSTSCHEDULE_CREATE / LIST / DELETE
/api/extension-bridge/aiPOSTAI_COMPLETE
/api/extension-bridge/access-statusPOSTGET_ACCESS_STATUS
/api/extension-bridge/company-usersPOSTGET_COMPANY_USERS
/api/extensions/[slug]/report-usagePOSTREPORT_USAGE
/api/developer/submissionsGET/POSTהגשות
/api/developer/upload-bundlePOSTהעלאת bundle
/api/developer/extension-dataGETGET_EXTENSION_DATA
/api/developer/platform-promptGETהפרומפט לפיתוח
/api/admin/extensionsGET/POSTניהול הרחבות (אדמין)
/api/admin/extensions/[id]PATCHעדכון הרחבה
/api/admin/extensions/[id]/accessGET/POST/DELETEניהול גישות
/api/admin/extensions/[id]/ai-configGET/PUTהגדרות AI
/api/admin/extensions/[id]/dictionaryGETמילון תרגומים