{"_id":"@bluelamp/share-sdk","name":"@bluelamp/share-sdk","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@bluelamp/share-sdk","version":"0.1.0","description":"BlueLamp テナント MCP 向けレコード共有 SDK: shared_with TEXT[] パターンで個別レコードを email 単位で共有する API","keywords":["bluelamp","mcp","share","prisma","rbac"],"license":"MIT","author":{"name":"BlueLamp","email":"dev@bluelamp.app"},"main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"scripts":{"build":"tsc","test":"jest","prepublishOnly":"npm run build && npm test"},"peerDependencies":{"@prisma/client":"^5.0.0 || ^6.0.0"},"peerDependenciesMeta":{"@prisma/client":{"optional":true}},"devDependencies":{"@prisma/client":"^5.22.0","@types/jest":"^29.5.12","@types/node":"^20.11.0","jest":"^29.7.0","ts-jest":"^29.1.2","typescript":"^5.3.3"},"engines":{"node":">=18"},"_id":"@bluelamp/share-sdk@0.1.0","gitHead":"0d3977d44743444a4cfe08e09a9756d0ffcbf097","_nodeVersion":"24.10.0","_npmVersion":"11.6.0","dist":{"integrity":"sha512-0v810YLKhxGQDG1H92xuY5GrLtaS3Ho1NARaKrWX9UDvOTfe0/MLD+Zx3l75qiSwwicpx7uxBZyzq+BMbWhqIg==","shasum":"f6f2f3f6107e31488c094c57884f61a5e066adca","tarball":"https://registry.npmjs.org/@bluelamp/share-sdk/-/share-sdk-0.1.0.tgz","fileCount":23,"unpackedSize":35559,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCZjpbvE6pBVbSB60OcBoGCLVY9CRBUB1u1LAxw4Py9tQIhAJ8uA40chUGksN+PJOh3zSbQOvTHWmUcedkQPZlX9MtN"}]},"_npmUser":{"name":"bluelamp","email":"shiraishi.tatsuya@mikoto.co.jp"},"directories":{},"maintainers":[{"name":"bluelamp","email":"shiraishi.tatsuya@mikoto.co.jp"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/share-sdk_0.1.0_1778742745124_0.3320217501087739"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-14T07:12:25.022Z","0.1.0":"2026-05-14T07:12:25.305Z","modified":"2026-05-14T07:12:25.565Z"},"maintainers":[{"name":"bluelamp","email":"shiraishi.tatsuya@mikoto.co.jp"}],"description":"BlueLamp テナント MCP 向けレコード共有 SDK: shared_with TEXT[] パターンで個別レコードを email 単位で共有する API","keywords":["bluelamp","mcp","share","prisma","rbac"],"author":{"name":"BlueLamp","email":"dev@bluelamp.app"},"license":"MIT","readme":"# @bluelamp/share-sdk\n\nBlueLamp テナント MCP 向けの **レコード共有 SDK**。\n\n「MCP が扱う個別レコード（メモ 1 件 / シークレット 1 件 / 経費 1 件など）を、BlueLamp ユーザーのメールアドレスで共有する」処理を 4 つの関数に標準化します。\n\n- `canAccess(record, user)` — 個別アクセス可否判定\n- `addShare(prisma, table, id, email)` — 共有先追加（冪等）\n- `removeShare(prisma, table, id, email)` — 共有先削除（冪等）\n- `replaceShares(prisma, table, id, emails)` — 共有先一括置換\n- `buildShareWhereClause(user)` — Prisma `findMany` 用 WHERE 句\n\n各テナント企業の MCP は本パッケージを `npm install` するだけで、ミコトの ainotes / password-manager と同じ共有 UX を実装できます。\n\n## インストール\n\n```bash\nnpm install @bluelamp/share-sdk @prisma/client\n```\n\nNode.js 18+ が必要です。\n\n## クイックスタート\n\n```ts\nimport express from 'express';\nimport { bluelampAuth } from '@bluelamp/auth-sdk';\nimport {\n  canAccess,\n  addShare,\n  removeShare,\n  buildShareWhereClause,\n} from '@bluelamp/share-sdk';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\nconst app = express();\napp.use(express.json());\n\napp.use('/api/attendance', bluelampAuth({\n  jwksUri: 'https://motohi-api.example/.well-known/jwks.json',\n  audience: 'https://x-corp-mcp.example',\n  requiredScope: 'attendance:read',\n}));\n\n// 取得（自分 or 共有された人のみ閲覧可）\napp.get('/api/attendance/:id', async (req, res) => {\n  const record = await prisma.attendance.findUnique({ where: { id: req.params.id } });\n  if (!record || !canAccess(record, req.user!)) return res.status(403).end();\n  res.json(record);\n});\n\n// 一覧（自分が見れるものだけ）\napp.get('/api/attendance', async (req, res) => {\n  const list = await prisma.attendance.findMany({\n    where: buildShareWhereClause(req.user!),\n    orderBy: { date: 'desc' },\n  });\n  res.json(list);\n});\n\n// 共有追加（owner のみ）\napp.post('/api/attendance/:id/share', async (req, res) => {\n  const record = await prisma.attendance.findUnique({ where: { id: req.params.id } });\n  if (record?.createdBy !== req.user!.id) return res.status(403).end();\n  res.json(await addShare(prisma, 'attendance', req.params.id, req.body.email));\n});\n\n// 共有削除（owner のみ）\napp.delete('/api/attendance/:id/share/:email', async (req, res) => {\n  const record = await prisma.attendance.findUnique({ where: { id: req.params.id } });\n  if (record?.createdBy !== req.user!.id) return res.status(403).end();\n  res.json(await removeShare(prisma, 'attendance', req.params.id, req.params.email));\n});\n```\n\n## テーブル設計\n\n新規テーブルは以下の規約を満たしてください:\n\n```sql\nCREATE TABLE your_record (\n  id          UUID PRIMARY KEY,\n  created_by  UUID NOT NULL,             -- owner ユーザー ID\n  shared_with TEXT[] DEFAULT '{}',       -- 共有先 email 配列\n  -- 業務カラム --\n  ...\n);\nCREATE INDEX idx_your_record_shared_with ON your_record USING GIN (shared_with);\n```\n\n既存テーブルがすでに `allowedOperators` / `owner_id` 等の別名を使っている場合は、SDK の `columnName` / `ownerProperty` / `sharedProperty` / `ownerColumn` / `sharedColumn` オプションで差し替えられます。後方互換のためにマイグレーションは不要です。\n\n```ts\n// 例: password-manager の vault_secrets.allowedOperators をそのまま使う\ncanAccess(record, user, { ownerProperty: 'createdBy', sharedProperty: 'allowedOperators' });\naddShare(prisma, 'vaultSecret', id, email, { columnName: 'allowedOperators' });\nbuildShareWhereClause(user, { sharedColumn: 'allowedOperators' });\n```\n\n## API リファレンス\n\n### `canAccess(record, user, options?): boolean`\n\nレコードに対するアクセス可否を判定します。\n\n判定順序:\n1. `user.rank` が `adminRanks`（default `['admin']`）のいずれかと一致 → `true`\n2. `record[ownerProperty]` が `user.id` と一致 → `true`\n3. `record[ownerProperty]` が `user.email` と一致 → `true`（owner が email で保存されているレガシーケース）\n4. `record[sharedProperty]` 配列に `user.email` が含まれる（**case-insensitive**, trim あり）→ `true`\n5. それ以外 → `false`\n\nオプション:\n- `ownerProperty` (default `'createdBy'`)\n- `sharedProperty` (default `'shared_with'`)\n- `adminRanks` (default `['admin']`)\n\n### `addShare(prisma, tableName, recordId, email, options?): Promise<{ added, shared_with }>`\n\n共有先 email を追加（**冪等**: 既に共有済みなら `added: false`）。\n\nオプション:\n- `columnName` (default `'shared_with'`)\n- `idColumnName` (default `'id'`)\n\n### `removeShare(prisma, tableName, recordId, email, options?): Promise<{ removed, shared_with }>`\n\n共有先 email を削除（**冪等**: 存在しなければ `removed: false`）。\n\n### `replaceShares(prisma, tableName, recordId, emails, options?): Promise<{ shared_with }>`\n\n共有先 email 配列をまるごと置換。重複と空文字は自動除外（case-insensitive dedup）。\n\n### `buildShareWhereClause(user, options?): { OR: [...] }`\n\nPrisma の `where` 句に追加するフィルタを返します。\n\n```ts\n// 戻り値の例\n{ OR: [\n  { createdBy: 'u-1' },\n  { createdBy: 'me@example.com' },\n  { shared_with: { has: 'me@example.com' } },\n] }\n```\n\nオプション:\n- `ownerColumn` (default `'createdBy'`)\n- `sharedColumn` (default `'shared_with'`)\n\n他の条件と組み合わせるときは `AND` で包んでください:\n\n```ts\nconst where = { AND: [{ category: 'memo' }, buildShareWhereClause(user)] };\n```\n\n## 同時更新（race condition）\n\n`addShare` / `removeShare` は内部で「読む → 配列を書き換える → 書き戻す」を行います。\n**完全な atomic ではない** 点に注意してください:\n\n- 2 ユーザーが同時に異なる email を追加すると、片方が上書きされる可能性があります\n- ただし同じ email を二重追加して壊れることはありません（読み取り側で `has` 判定するため）\n\n厳密な atomic が必要な MCP は、SDK の代わりに raw SQL で `array_append()` + `WHERE NOT shared_with @> ARRAY[$1]` を使ってください。\n\n## ライセンス\n\nMIT\n\n## 関連\n\n- 規約 + 詳細ガイド: `docs/share-sdk-guide.md`（bluelampportal monorepo）\n- 認証 SDK: [`@bluelamp/auth-sdk`](https://www.npmjs.com/package/@bluelamp/auth-sdk)\n","readmeFilename":"README.md","_rev":"1-ba118b5f8116ee9da4a596a12d064e09"}