@shipfox/client-secrets 4.0.0 → 6.0.1

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 (82) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +46 -0
  3. package/dist/components/form-errors.d.ts.map +1 -1
  4. package/dist/components/form-errors.js +0 -6
  5. package/dist/components/form-errors.js.map +1 -1
  6. package/dist/components/secret-form.d.ts.map +1 -1
  7. package/dist/components/secret-form.js +6 -8
  8. package/dist/components/secret-form.js.map +1 -1
  9. package/dist/components/variable-form.d.ts.map +1 -1
  10. package/dist/components/variable-form.js +6 -8
  11. package/dist/components/variable-form.js.map +1 -1
  12. package/dist/components/workspace-secrets-section.d.ts.map +1 -1
  13. package/dist/components/workspace-secrets-section.js +4 -2
  14. package/dist/components/workspace-secrets-section.js.map +1 -1
  15. package/dist/components/workspace-secrets-section.stories.d.ts.map +1 -1
  16. package/dist/components/workspace-secrets-section.stories.js.map +1 -1
  17. package/dist/components/workspace-variables-section.d.ts.map +1 -1
  18. package/dist/components/workspace-variables-section.js +5 -3
  19. package/dist/components/workspace-variables-section.js.map +1 -1
  20. package/dist/components/workspace-variables-section.stories.d.ts.map +1 -1
  21. package/dist/components/workspace-variables-section.stories.js.map +1 -1
  22. package/dist/core/store.d.ts +54 -0
  23. package/dist/core/store.d.ts.map +1 -0
  24. package/dist/core/store.js +44 -0
  25. package/dist/core/store.js.map +1 -0
  26. package/dist/feature.d.ts +39 -0
  27. package/dist/feature.d.ts.map +1 -0
  28. package/dist/feature.js +35 -0
  29. package/dist/feature.js.map +1 -0
  30. package/dist/hooks/api/create-store-api.d.ts +31 -49
  31. package/dist/hooks/api/create-store-api.d.ts.map +1 -1
  32. package/dist/hooks/api/create-store-api.js +44 -45
  33. package/dist/hooks/api/create-store-api.js.map +1 -1
  34. package/dist/hooks/api/secrets.d.ts +16 -55
  35. package/dist/hooks/api/secrets.d.ts.map +1 -1
  36. package/dist/hooks/api/secrets.js +10 -2
  37. package/dist/hooks/api/secrets.js.map +1 -1
  38. package/dist/hooks/api/store-mapper.d.ts +7 -0
  39. package/dist/hooks/api/store-mapper.d.ts.map +1 -0
  40. package/dist/hooks/api/store-mapper.js +36 -0
  41. package/dist/hooks/api/store-mapper.js.map +1 -0
  42. package/dist/hooks/api/variables.d.ts +25 -78
  43. package/dist/hooks/api/variables.d.ts.map +1 -1
  44. package/dist/hooks/api/variables.js +37 -28
  45. package/dist/hooks/api/variables.js.map +1 -1
  46. package/dist/routes/secrets-settings.d.ts +5 -0
  47. package/dist/routes/secrets-settings.d.ts.map +1 -0
  48. package/dist/routes/secrets-settings.js +10 -0
  49. package/dist/routes/secrets-settings.js.map +1 -0
  50. package/dist/routes/variables-settings.d.ts +5 -0
  51. package/dist/routes/variables-settings.d.ts.map +1 -0
  52. package/dist/routes/variables-settings.js +10 -0
  53. package/dist/routes/variables-settings.js.map +1 -0
  54. package/dist/tsconfig.test.tsbuildinfo +1 -1
  55. package/package.json +17 -9
  56. package/src/components/form-errors.test.ts +2 -2
  57. package/src/components/form-errors.ts +0 -3
  58. package/src/components/secret-form.tsx +15 -5
  59. package/src/components/variable-form.tsx +15 -5
  60. package/src/components/workspace-secrets-section.stories.tsx +1 -2
  61. package/src/components/workspace-secrets-section.tsx +8 -4
  62. package/src/components/workspace-variables-section.stories.tsx +1 -2
  63. package/src/components/workspace-variables-section.tsx +10 -6
  64. package/src/core/store.test.ts +38 -0
  65. package/src/core/store.ts +91 -0
  66. package/src/feature.ts +29 -0
  67. package/src/hooks/api/create-store-api.ts +104 -75
  68. package/src/hooks/api/secrets.test.ts +16 -10
  69. package/src/hooks/api/secrets.ts +11 -14
  70. package/src/hooks/api/store-mapper.test.ts +39 -0
  71. package/src/hooks/api/store-mapper.ts +47 -0
  72. package/src/hooks/api/variables.ts +52 -36
  73. package/src/routes/secrets-settings.tsx +6 -0
  74. package/src/routes/variables-settings.tsx +6 -0
  75. package/tsconfig.build.tsbuildinfo +1 -1
  76. package/vercel.json +5 -0
  77. package/dist/components/store-key.d.ts +0 -20
  78. package/dist/components/store-key.d.ts.map +0 -1
  79. package/dist/components/store-key.js +0 -25
  80. package/dist/components/store-key.js.map +0 -1
  81. package/src/components/store-key.test.ts +0 -53
  82. package/src/components/store-key.ts +0 -36
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workspace-variables-section.tsx"],"sourcesContent":["import type {VariableListItemDto} from '@shipfox/api-secrets-dto';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button, IconButton} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';\nimport {RelativeTime, RelativeTimeProvider} from '@shipfox/react-ui/relative-time';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@shipfox/react-ui/table';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {useMemo, useState} from 'react';\nimport {useDeleteVariableMutation, useVariablesQuery} from '#hooks/api/variables.js';\nimport {copyKeyName} from './copy-key.js';\nimport {DeleteEntryDialog} from './delete-entry-dialog.js';\nimport {secretsErrorToFormError} from './form-errors.js';\nimport {StoreRowsSkeleton, StoreSurface} from './store-section-shell.js';\nimport {VariableForm} from './variable-form.js';\n\nconst VARIABLES_DESCRIPTION =\n 'Plaintext configuration values for non-sensitive data like regions, flags, and log levels.';\nconst EMPTY_VARIABLES_DESCRIPTION =\n 'Create a variable to store non-sensitive configuration like regions, flags, and log levels.';\n\ntype FormState = {mode: 'create'} | {mode: 'edit'; variable: VariableListItemDto} | null;\n\nexport function WorkspaceVariablesSection({workspaceId}: {workspaceId: string}) {\n const variablesQuery = useVariablesQuery(workspaceId);\n const deleteVariable = useDeleteVariableMutation();\n const [formState, setFormState] = useState<FormState>(null);\n const [deleteKey, setDeleteKey] = useState<string | null>(null);\n const [deleteError, setDeleteError] = useState<string | undefined>();\n\n const variables = useMemo(\n () => [...(variablesQuery.data ?? [])].sort((a, b) => a.key.localeCompare(b.key)),\n [variablesQuery.data],\n );\n\n function closeDelete() {\n setDeleteKey(null);\n setDeleteError(undefined);\n }\n\n return (\n <RelativeTimeProvider>\n <section className=\"flex flex-col gap-16\" aria-label=\"Variables\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Variables</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {VARIABLES_DESCRIPTION}\n </Text>\n </div>\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create variable\n </Button>\n </div>\n\n {variablesQuery.isPending ? <StoreRowsSkeleton label=\"Loading variables\" /> : null}\n\n {variablesQuery.isError && variablesQuery.data === undefined ? (\n <StoreSurface className=\"px-16\">\n <QueryLoadError query={variablesQuery} subject=\"variables\" />\n </StoreSurface>\n ) : null}\n\n {variablesQuery.data !== undefined && variables.length === 0 ? (\n <StoreSurface className=\"px-16\">\n <EmptyState\n icon=\"bracesLine\"\n title=\"No variables yet\"\n description={EMPTY_VARIABLES_DESCRIPTION}\n action={\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create variable\n </Button>\n }\n />\n </StoreSurface>\n ) : null}\n\n {variables.length > 0 ? (\n <StoreSurface>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Value</TableHead>\n <TableHead>Last edited</TableHead>\n <TableHead className=\"w-40 text-right\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {variables.map((variable) => (\n <VariableRow\n key={variable.key}\n variable={variable}\n onEdit={() => setFormState({mode: 'edit', variable})}\n onDelete={() => setDeleteKey(variable.key)}\n />\n ))}\n </TableBody>\n </Table>\n </StoreSurface>\n ) : null}\n </section>\n\n <Modal\n open={formState !== null}\n onOpenChange={(open) => {\n if (!open) setFormState(null);\n }}\n >\n <ModalContent>\n <ModalHeader>\n <ModalTitle>\n {formState?.mode === 'edit' ? 'Update variable' : 'Create variable'}\n </ModalTitle>\n </ModalHeader>\n {formState ? (\n <VariableForm\n workspaceId={workspaceId}\n mode={formState.mode}\n existingKey={formState.mode === 'edit' ? formState.variable.key : undefined}\n existingValue={formState.mode === 'edit' ? formState.variable.value : undefined}\n existingValueTruncated={\n formState.mode === 'edit' ? formState.variable.value_truncated : undefined\n }\n reservedKeys={variables.map((variable) => variable.key)}\n onSaved={() => {\n const wasEdit = formState.mode === 'edit';\n setFormState(null);\n toast.success(wasEdit ? 'Variable updated' : 'Variable created');\n }}\n onCancel={() => setFormState(null)}\n />\n ) : null}\n </ModalContent>\n </Modal>\n\n <DeleteEntryDialog\n open={deleteKey !== null}\n onOpenChange={(open) => {\n if (!open) closeDelete();\n }}\n entryKey={deleteKey ?? ''}\n isLoading={deleteVariable.isPending}\n errorMessage={deleteError}\n onConfirm={async () => {\n if (deleteKey === null) return;\n setDeleteError(undefined);\n try {\n await deleteVariable.mutateAsync({workspaceId, key: deleteKey});\n toast.success('Variable deleted');\n closeDelete();\n } catch (error) {\n setDeleteError(secretsErrorToFormError(error).message);\n }\n }}\n />\n </RelativeTimeProvider>\n );\n}\n\nfunction VariableRow({\n variable,\n onEdit,\n onDelete,\n}: {\n variable: VariableListItemDto;\n onEdit: () => void;\n onDelete: () => void;\n}) {\n return (\n <TableRow>\n <TableCell>\n <button\n type=\"button\"\n className=\"inline-flex min-w-0 cursor-pointer rounded-4 border-none bg-transparent p-0 text-left text-foreground-neutral-base outline-none transition-colors hover:text-foreground-highlight-interactive focus-visible:shadow-border-interactive-with-active\"\n aria-label={`Copy variable name ${variable.key}`}\n onClick={() => void copyKeyName(variable.key)}\n >\n <Code as=\"span\" variant=\"paragraph\" className=\"truncate\">\n {variable.key}\n </Code>\n </button>\n </TableCell>\n <TableCell>\n <span\n title={variable.value}\n className=\"block max-w-[280px] truncate font-code text-foreground-neutral-base\"\n >\n {variable.value === '' ? (\n <span className=\"text-foreground-neutral-muted\">(empty)</span>\n ) : (\n variable.value\n )}\n </span>\n </TableCell>\n <TableCell className=\"text-foreground-neutral-muted\">\n <RelativeTime value={variable.updated_at} />\n </TableCell>\n <TableCell className=\"text-right\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n aria-label={`Actions for ${variable.key}`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem icon=\"editLine\" onSelect={onEdit}>\n Edit value\n </DropdownMenuItem>\n <DropdownMenuItem icon=\"deleteBinLine\" onSelect={onDelete}>\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n );\n}\n"],"names":["QueryLoadError","Button","IconButton","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","EmptyState","Modal","ModalContent","ModalHeader","ModalTitle","RelativeTime","RelativeTimeProvider","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","useMemo","useState","useDeleteVariableMutation","useVariablesQuery","copyKeyName","DeleteEntryDialog","secretsErrorToFormError","StoreRowsSkeleton","StoreSurface","VariableForm","VARIABLES_DESCRIPTION","EMPTY_VARIABLES_DESCRIPTION","WorkspaceVariablesSection","workspaceId","variablesQuery","deleteVariable","formState","setFormState","deleteKey","setDeleteKey","deleteError","setDeleteError","variables","data","sort","a","b","key","localeCompare","closeDelete","undefined","section","className","aria-label","div","variant","size","onClick","mode","isPending","label","isError","query","subject","length","icon","title","description","action","span","map","variable","VariableRow","onEdit","onDelete","open","onOpenChange","existingKey","existingValue","value","existingValueTruncated","value_truncated","reservedKeys","onSaved","wasEdit","success","onCancel","entryKey","isLoading","errorMessage","onConfirm","mutateAsync","error","message","button","type","as","updated_at","asChild","align","onSelect"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/components/workspace-variables-section.tsx"],"sourcesContent":["import {QueryLoadError} from '@shipfox/client-ui';\nimport {Button, IconButton} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';\nimport {RelativeTime, RelativeTimeProvider} from '@shipfox/react-ui/relative-time';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@shipfox/react-ui/table';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {useMemo, useState} from 'react';\nimport {type VariablePreview, workspaceStoreScope} from '#core/store.js';\nimport {useDeleteVariableMutation, useVariablesQuery} from '#hooks/api/variables.js';\nimport {copyKeyName} from './copy-key.js';\nimport {DeleteEntryDialog} from './delete-entry-dialog.js';\nimport {secretsErrorToFormError} from './form-errors.js';\nimport {StoreRowsSkeleton, StoreSurface} from './store-section-shell.js';\nimport {VariableForm} from './variable-form.js';\n\nconst VARIABLES_DESCRIPTION =\n 'Plaintext configuration values for non-sensitive data like regions, flags, and log levels.';\nconst EMPTY_VARIABLES_DESCRIPTION =\n 'Create a variable to store non-sensitive configuration like regions, flags, and log levels.';\n\ntype FormState = {mode: 'create'} | {mode: 'edit'; variable: VariablePreview} | null;\n\nexport function WorkspaceVariablesSection({workspaceId}: {workspaceId: string}) {\n const variablesQuery = useVariablesQuery(workspaceId);\n const deleteVariable = useDeleteVariableMutation();\n const [formState, setFormState] = useState<FormState>(null);\n const [deleteKey, setDeleteKey] = useState<string | null>(null);\n const [deleteError, setDeleteError] = useState<string | undefined>();\n\n const variables = useMemo(\n () => [...(variablesQuery.data ?? [])].sort((a, b) => a.key.localeCompare(b.key)),\n [variablesQuery.data],\n );\n\n function closeDelete() {\n setDeleteKey(null);\n setDeleteError(undefined);\n }\n\n return (\n <RelativeTimeProvider>\n <section className=\"flex flex-col gap-16\" aria-label=\"Variables\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Variables</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {VARIABLES_DESCRIPTION}\n </Text>\n </div>\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create variable\n </Button>\n </div>\n\n {variablesQuery.isPending ? <StoreRowsSkeleton label=\"Loading variables\" /> : null}\n\n {variablesQuery.isError && variablesQuery.data === undefined ? (\n <StoreSurface className=\"px-16\">\n <QueryLoadError query={variablesQuery} subject=\"variables\" />\n </StoreSurface>\n ) : null}\n\n {variablesQuery.data !== undefined && variables.length === 0 ? (\n <StoreSurface className=\"px-16\">\n <EmptyState\n icon=\"bracesLine\"\n title=\"No variables yet\"\n description={EMPTY_VARIABLES_DESCRIPTION}\n action={\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create variable\n </Button>\n }\n />\n </StoreSurface>\n ) : null}\n\n {variables.length > 0 ? (\n <StoreSurface>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Value</TableHead>\n <TableHead>Last edited</TableHead>\n <TableHead className=\"w-40 text-right\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {variables.map((variable) => (\n <VariableRow\n key={variable.key}\n variable={variable}\n onEdit={() => setFormState({mode: 'edit', variable})}\n onDelete={() => setDeleteKey(variable.key)}\n />\n ))}\n </TableBody>\n </Table>\n </StoreSurface>\n ) : null}\n </section>\n\n <Modal\n open={formState !== null}\n onOpenChange={(open) => {\n if (!open) setFormState(null);\n }}\n >\n <ModalContent>\n <ModalHeader>\n <ModalTitle>\n {formState?.mode === 'edit' ? 'Update variable' : 'Create variable'}\n </ModalTitle>\n </ModalHeader>\n {formState ? (\n <VariableForm\n workspaceId={workspaceId}\n mode={formState.mode}\n existingKey={formState.mode === 'edit' ? formState.variable.key : undefined}\n existingValue={formState.mode === 'edit' ? formState.variable.value : undefined}\n existingValueTruncated={\n formState.mode === 'edit' ? formState.variable.valueTruncated : undefined\n }\n reservedKeys={variables.map((variable) => variable.key)}\n onSaved={() => {\n const wasEdit = formState.mode === 'edit';\n setFormState(null);\n toast.success(wasEdit ? 'Variable updated' : 'Variable created');\n }}\n onCancel={() => setFormState(null)}\n />\n ) : null}\n </ModalContent>\n </Modal>\n\n <DeleteEntryDialog\n open={deleteKey !== null}\n onOpenChange={(open) => {\n if (!open) closeDelete();\n }}\n entryKey={deleteKey ?? ''}\n isLoading={deleteVariable.isPending}\n errorMessage={deleteError}\n onConfirm={async () => {\n if (deleteKey === null) return;\n setDeleteError(undefined);\n try {\n await deleteVariable.mutateAsync({\n workspaceId,\n key: deleteKey,\n scope: workspaceStoreScope,\n });\n toast.success('Variable deleted');\n closeDelete();\n } catch (error) {\n setDeleteError(secretsErrorToFormError(error).message);\n }\n }}\n />\n </RelativeTimeProvider>\n );\n}\n\nfunction VariableRow({\n variable,\n onEdit,\n onDelete,\n}: {\n variable: VariablePreview;\n onEdit: () => void;\n onDelete: () => void;\n}) {\n return (\n <TableRow>\n <TableCell>\n <button\n type=\"button\"\n className=\"inline-flex min-w-0 cursor-pointer rounded-4 border-none bg-transparent p-0 text-left text-foreground-neutral-base outline-none transition-colors hover:text-foreground-highlight-interactive focus-visible:shadow-border-interactive-with-active\"\n aria-label={`Copy variable name ${variable.key}`}\n onClick={() => void copyKeyName(variable.key)}\n >\n <Code as=\"span\" variant=\"paragraph\" className=\"truncate\">\n {variable.key}\n </Code>\n </button>\n </TableCell>\n <TableCell>\n <span\n title={variable.value}\n className=\"block max-w-[280px] truncate font-code text-foreground-neutral-base\"\n >\n {variable.value === '' ? (\n <span className=\"text-foreground-neutral-muted\">(empty)</span>\n ) : (\n variable.value\n )}\n </span>\n </TableCell>\n <TableCell className=\"text-foreground-neutral-muted\">\n <RelativeTime value={variable.updatedAt} />\n </TableCell>\n <TableCell className=\"text-right\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n aria-label={`Actions for ${variable.key}`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem icon=\"editLine\" onSelect={onEdit}>\n Edit value\n </DropdownMenuItem>\n <DropdownMenuItem icon=\"deleteBinLine\" onSelect={onDelete}>\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n );\n}\n"],"names":["QueryLoadError","Button","IconButton","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","EmptyState","Modal","ModalContent","ModalHeader","ModalTitle","RelativeTime","RelativeTimeProvider","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","useMemo","useState","workspaceStoreScope","useDeleteVariableMutation","useVariablesQuery","copyKeyName","DeleteEntryDialog","secretsErrorToFormError","StoreRowsSkeleton","StoreSurface","VariableForm","VARIABLES_DESCRIPTION","EMPTY_VARIABLES_DESCRIPTION","WorkspaceVariablesSection","workspaceId","variablesQuery","deleteVariable","formState","setFormState","deleteKey","setDeleteKey","deleteError","setDeleteError","variables","data","sort","a","b","key","localeCompare","closeDelete","undefined","section","className","aria-label","div","variant","size","onClick","mode","isPending","label","isError","query","subject","length","icon","title","description","action","span","map","variable","VariableRow","onEdit","onDelete","open","onOpenChange","existingKey","existingValue","value","existingValueTruncated","valueTruncated","reservedKeys","onSaved","wasEdit","success","onCancel","entryKey","isLoading","errorMessage","onConfirm","mutateAsync","scope","error","message","button","type","as","updatedAt","asChild","align","onSelect"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-variables-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/workspace-variables-section.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,IAAI,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AA+DhE,KAAK,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAcnC,iBAAS,YAAY,CAAC,EAAC,QAAQ,EAAC,EAAE;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAC,+BAcrD;AAED,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAInC,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,eAAO,MAAM,MAAM,EAAE,KAQpB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAKnB,CAAC"}
1
+ {"version":3,"file":"workspace-variables-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/workspace-variables-section.stories.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAY,IAAI,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AA+DhE,KAAK,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAcnC,iBAAS,YAAY,CAAC,EAAC,QAAQ,EAAC,EAAE;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAC,+BAcrD;AAED,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAInC,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,eAAO,MAAM,MAAM,EAAE,KAQpB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAKnB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workspace-variables-section.stories.tsx"],"sourcesContent":["import type {VariableListItemDto} from '@shipfox/api-secrets-dto';\nimport {configureApiClient} from '@shipfox/client-api';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Decorator, Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {useEffect, useMemo} from 'react';\nimport {within} from 'storybook/test';\nimport {WorkspaceVariablesSection} from './workspace-variables-section.js';\n\n// Freeze the clock only while a story is mounted so RelativeTime renders a stable\n// string in argos snapshots, without leaking the override to other stories.\nconst FROZEN_NOW = Date.parse('2026-07-02T00:00:00.000Z');\nconst realDateNow = Date.now;\nconst withFrozenClock: Decorator = (Story) => {\n Date.now = () => FROZEN_NOW;\n useEffect(\n () => () => {\n Date.now = realDateNow;\n },\n [],\n );\n return <Story />;\n};\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\nconst EDITOR_ID = '22222222-2222-4222-8222-222222222222';\n\nconst VARIABLES: VariableListItemDto[] = [\n {\n key: 'LOG_LEVEL',\n value: 'debug',\n value_truncated: false,\n project_id: null,\n created_at: '2026-05-01T10:00:00.000Z',\n updated_at: '2026-06-30T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'FEATURE_FLAG',\n value: '',\n value_truncated: false,\n project_id: null,\n created_at: '2026-04-01T10:00:00.000Z',\n updated_at: '2026-06-25T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'TLS_CERT',\n value: '-----BEGIN CERTIFICATE-----',\n value_truncated: true,\n project_id: null,\n created_at: '2026-03-15T10:00:00.000Z',\n updated_at: '2026-06-22T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'REGION',\n value: 'eu-west-1',\n value_truncated: false,\n project_id: null,\n created_at: '2026-03-01T10:00:00.000Z',\n updated_at: '2026-06-20T12:00:00.000Z',\n last_edited_by: null,\n },\n];\n\ntype Scenario = 'loaded' | 'empty';\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return (() => {\n const variables = scenario === 'empty' ? [] : VARIABLES;\n return Promise.resolve(\n new Response(JSON.stringify({variables, next_cursor: null}), {\n status: 200,\n headers: {'content-type': 'application/json'},\n }),\n );\n }) as unknown as typeof fetch;\n}\n\nfunction SectionStory({scenario}: {scenario: Scenario}) {\n configureApiClient({baseUrl: 'https://api.example.test', fetchImpl: fetchForScenario(scenario)});\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n return (\n <QueryClientProvider client={queryClient}>\n <div className=\"w-[720px] p-24\">\n <WorkspaceVariablesSection workspaceId={WORKSPACE_ID} />\n </div>\n <Toaster />\n </QueryClientProvider>\n );\n}\n\nconst meta: Meta<typeof SectionStory> = {\n title: 'Secrets/WorkspaceVariablesSection',\n component: SectionStory,\n decorators: [withFrozenClock],\n};\nexport default meta;\n\ntype Story = StoryObj<typeof SectionStory>;\n\nexport const Loaded: Story = {\n args: {scenario: 'loaded'},\n play: async ({canvasElement}) => {\n const canvas = within(canvasElement);\n\n await canvas.findByText('LOG_LEVEL');\n await canvas.findByRole('button', {name: 'Copy variable name LOG_LEVEL'});\n },\n};\n\nexport const Empty: Story = {\n args: {scenario: 'empty'},\n play: async ({canvasElement}) => {\n await within(canvasElement).findByText('No variables yet');\n },\n};\n"],"names":["configureApiClient","Toaster","QueryClient","QueryClientProvider","useEffect","useMemo","within","WorkspaceVariablesSection","FROZEN_NOW","Date","parse","realDateNow","now","withFrozenClock","Story","WORKSPACE_ID","EDITOR_ID","VARIABLES","key","value","value_truncated","project_id","created_at","updated_at","last_edited_by","fetchForScenario","scenario","variables","Promise","resolve","Response","JSON","stringify","next_cursor","status","headers","SectionStory","baseUrl","fetchImpl","queryClient","defaultOptions","queries","retry","client","div","className","workspaceId","meta","title","component","decorators","Loaded","args","play","canvasElement","canvas","findByText","findByRole","name","Empty"],"mappings":";AACA,SAAQA,kBAAkB,QAAO,sBAAsB;AACvD,SAAQC,OAAO,QAAO,0BAA0B;AAEhD,SAAQC,WAAW,EAAEC,mBAAmB,QAAO,wBAAwB;AACvE,SAAQC,SAAS,EAAEC,OAAO,QAAO,QAAQ;AACzC,SAAQC,MAAM,QAAO,iBAAiB;AACtC,SAAQC,yBAAyB,QAAO,mCAAmC;AAE3E,kFAAkF;AAClF,4EAA4E;AAC5E,MAAMC,aAAaC,KAAKC,KAAK,CAAC;AAC9B,MAAMC,cAAcF,KAAKG,GAAG;AAC5B,MAAMC,kBAA6B,CAACC;IAClCL,KAAKG,GAAG,GAAG,IAAMJ;IACjBJ,UACE,IAAM;YACJK,KAAKG,GAAG,GAAGD;QACb,GACA,EAAE;IAEJ,qBAAO,KAACG;AACV;AAEA,MAAMC,eAAe;AACrB,MAAMC,YAAY;AAElB,MAAMC,YAAmC;IACvC;QACEC,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgB;IAClB;CACD;AAID,SAASC,iBAAiBC,QAAkB;IAC1C,OAAQ;QACN,MAAMC,YAAYD,aAAa,UAAU,EAAE,GAAGT;QAC9C,OAAOW,QAAQC,OAAO,CACpB,IAAIC,SAASC,KAAKC,SAAS,CAAC;YAACL;YAAWM,aAAa;QAAI,IAAI;YAC3DC,QAAQ;YACRC,SAAS;gBAAC,gBAAgB;YAAkB;QAC9C;IAEJ;AACF;AAEA,SAASC,aAAa,EAACV,QAAQ,EAAuB;IACpD1B,mBAAmB;QAACqC,SAAS;QAA4BC,WAAWb,iBAAiBC;IAAS;IAC9F,MAAMa,cAAclC,QAClB,IAAM,IAAIH,YAAY;YAACsC,gBAAgB;gBAACC,SAAS;oBAACC,OAAO;gBAAK;YAAC;QAAC,IAChE,EAAE;IAEJ,qBACE,MAACvC;QAAoBwC,QAAQJ;;0BAC3B,KAACK;gBAAIC,WAAU;0BACb,cAAA,KAACtC;oBAA0BuC,aAAa/B;;;0BAE1C,KAACd;;;AAGP;AAEA,MAAM8C,OAAkC;IACtCC,OAAO;IACPC,WAAWb;IACXc,YAAY;QAACrC;KAAgB;AAC/B;AACA,eAAekC,KAAK;AAIpB,OAAO,MAAMI,SAAgB;IAC3BC,MAAM;QAAC1B,UAAU;IAAQ;IACzB2B,MAAM,OAAO,EAACC,aAAa,EAAC;QAC1B,MAAMC,SAASjD,OAAOgD;QAEtB,MAAMC,OAAOC,UAAU,CAAC;QACxB,MAAMD,OAAOE,UAAU,CAAC,UAAU;YAACC,MAAM;QAA8B;IACzE;AACF,EAAE;AAEF,OAAO,MAAMC,QAAe;IAC1BP,MAAM;QAAC1B,UAAU;IAAO;IACxB2B,MAAM,OAAO,EAACC,aAAa,EAAC;QAC1B,MAAMhD,OAAOgD,eAAeE,UAAU,CAAC;IACzC;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/components/workspace-variables-section.stories.tsx"],"sourcesContent":["import {configureApiClient} from '@shipfox/client-api';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Decorator, Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {useEffect, useMemo} from 'react';\nimport {within} from 'storybook/test';\nimport {WorkspaceVariablesSection} from './workspace-variables-section.js';\n\n// Freeze the clock only while a story is mounted so RelativeTime renders a stable\n// string in argos snapshots, without leaking the override to other stories.\nconst FROZEN_NOW = Date.parse('2026-07-02T00:00:00.000Z');\nconst realDateNow = Date.now;\nconst withFrozenClock: Decorator = (Story) => {\n Date.now = () => FROZEN_NOW;\n useEffect(\n () => () => {\n Date.now = realDateNow;\n },\n [],\n );\n return <Story />;\n};\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\nconst EDITOR_ID = '22222222-2222-4222-8222-222222222222';\n\nconst VARIABLES = [\n {\n key: 'LOG_LEVEL',\n value: 'debug',\n value_truncated: false,\n project_id: null,\n created_at: '2026-05-01T10:00:00.000Z',\n updated_at: '2026-06-30T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'FEATURE_FLAG',\n value: '',\n value_truncated: false,\n project_id: null,\n created_at: '2026-04-01T10:00:00.000Z',\n updated_at: '2026-06-25T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'TLS_CERT',\n value: '-----BEGIN CERTIFICATE-----',\n value_truncated: true,\n project_id: null,\n created_at: '2026-03-15T10:00:00.000Z',\n updated_at: '2026-06-22T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'REGION',\n value: 'eu-west-1',\n value_truncated: false,\n project_id: null,\n created_at: '2026-03-01T10:00:00.000Z',\n updated_at: '2026-06-20T12:00:00.000Z',\n last_edited_by: null,\n },\n];\n\ntype Scenario = 'loaded' | 'empty';\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return (() => {\n const variables = scenario === 'empty' ? [] : VARIABLES;\n return Promise.resolve(\n new Response(JSON.stringify({variables, next_cursor: null}), {\n status: 200,\n headers: {'content-type': 'application/json'},\n }),\n );\n }) as unknown as typeof fetch;\n}\n\nfunction SectionStory({scenario}: {scenario: Scenario}) {\n configureApiClient({baseUrl: 'https://api.example.test', fetchImpl: fetchForScenario(scenario)});\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n return (\n <QueryClientProvider client={queryClient}>\n <div className=\"w-[720px] p-24\">\n <WorkspaceVariablesSection workspaceId={WORKSPACE_ID} />\n </div>\n <Toaster />\n </QueryClientProvider>\n );\n}\n\nconst meta: Meta<typeof SectionStory> = {\n title: 'Secrets/WorkspaceVariablesSection',\n component: SectionStory,\n decorators: [withFrozenClock],\n};\nexport default meta;\n\ntype Story = StoryObj<typeof SectionStory>;\n\nexport const Loaded: Story = {\n args: {scenario: 'loaded'},\n play: async ({canvasElement}) => {\n const canvas = within(canvasElement);\n\n await canvas.findByText('LOG_LEVEL');\n await canvas.findByRole('button', {name: 'Copy variable name LOG_LEVEL'});\n },\n};\n\nexport const Empty: Story = {\n args: {scenario: 'empty'},\n play: async ({canvasElement}) => {\n await within(canvasElement).findByText('No variables yet');\n },\n};\n"],"names":["configureApiClient","Toaster","QueryClient","QueryClientProvider","useEffect","useMemo","within","WorkspaceVariablesSection","FROZEN_NOW","Date","parse","realDateNow","now","withFrozenClock","Story","WORKSPACE_ID","EDITOR_ID","VARIABLES","key","value","value_truncated","project_id","created_at","updated_at","last_edited_by","fetchForScenario","scenario","variables","Promise","resolve","Response","JSON","stringify","next_cursor","status","headers","SectionStory","baseUrl","fetchImpl","queryClient","defaultOptions","queries","retry","client","div","className","workspaceId","meta","title","component","decorators","Loaded","args","play","canvasElement","canvas","findByText","findByRole","name","Empty"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,sBAAsB;AACvD,SAAQC,OAAO,QAAO,0BAA0B;AAEhD,SAAQC,WAAW,EAAEC,mBAAmB,QAAO,wBAAwB;AACvE,SAAQC,SAAS,EAAEC,OAAO,QAAO,QAAQ;AACzC,SAAQC,MAAM,QAAO,iBAAiB;AACtC,SAAQC,yBAAyB,QAAO,mCAAmC;AAE3E,kFAAkF;AAClF,4EAA4E;AAC5E,MAAMC,aAAaC,KAAKC,KAAK,CAAC;AAC9B,MAAMC,cAAcF,KAAKG,GAAG;AAC5B,MAAMC,kBAA6B,CAACC;IAClCL,KAAKG,GAAG,GAAG,IAAMJ;IACjBJ,UACE,IAAM;YACJK,KAAKG,GAAG,GAAGD;QACb,GACA,EAAE;IAEJ,qBAAO,KAACG;AACV;AAEA,MAAMC,eAAe;AACrB,MAAMC,YAAY;AAElB,MAAMC,YAAY;IAChB;QACEC,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgBR;IAClB;IACA;QACEE,KAAK;QACLC,OAAO;QACPC,iBAAiB;QACjBC,YAAY;QACZC,YAAY;QACZC,YAAY;QACZC,gBAAgB;IAClB;CACD;AAID,SAASC,iBAAiBC,QAAkB;IAC1C,OAAQ;QACN,MAAMC,YAAYD,aAAa,UAAU,EAAE,GAAGT;QAC9C,OAAOW,QAAQC,OAAO,CACpB,IAAIC,SAASC,KAAKC,SAAS,CAAC;YAACL;YAAWM,aAAa;QAAI,IAAI;YAC3DC,QAAQ;YACRC,SAAS;gBAAC,gBAAgB;YAAkB;QAC9C;IAEJ;AACF;AAEA,SAASC,aAAa,EAACV,QAAQ,EAAuB;IACpD1B,mBAAmB;QAACqC,SAAS;QAA4BC,WAAWb,iBAAiBC;IAAS;IAC9F,MAAMa,cAAclC,QAClB,IAAM,IAAIH,YAAY;YAACsC,gBAAgB;gBAACC,SAAS;oBAACC,OAAO;gBAAK;YAAC;QAAC,IAChE,EAAE;IAEJ,qBACE,MAACvC;QAAoBwC,QAAQJ;;0BAC3B,KAACK;gBAAIC,WAAU;0BACb,cAAA,KAACtC;oBAA0BuC,aAAa/B;;;0BAE1C,KAACd;;;AAGP;AAEA,MAAM8C,OAAkC;IACtCC,OAAO;IACPC,WAAWb;IACXc,YAAY;QAACrC;KAAgB;AAC/B;AACA,eAAekC,KAAK;AAIpB,OAAO,MAAMI,SAAgB;IAC3BC,MAAM;QAAC1B,UAAU;IAAQ;IACzB2B,MAAM,OAAO,EAACC,aAAa,EAAC;QAC1B,MAAMC,SAASjD,OAAOgD;QAEtB,MAAMC,OAAOC,UAAU,CAAC;QACxB,MAAMD,OAAOE,UAAU,CAAC,UAAU;YAACC,MAAM;QAA8B;IACzE;AACF,EAAE;AAEF,OAAO,MAAMC,QAAe;IAC1BP,MAAM;QAAC1B,UAAU;IAAO;IACxB2B,MAAM,OAAO,EAACC,aAAa,EAAC;QAC1B,MAAMhD,OAAOgD,eAAeE,UAAU,CAAC;IACzC;AACF,EAAE"}
@@ -0,0 +1,54 @@
1
+ export declare const STORE_KEY_HELP = "Uppercase letters, digits and underscores; must start with a letter or underscore.";
2
+ export type StoreScope = {
3
+ kind: 'workspace';
4
+ } | {
5
+ kind: 'project';
6
+ projectId: string;
7
+ };
8
+ export declare const workspaceStoreScope: StoreScope;
9
+ export interface StoreMetadata {
10
+ key: string;
11
+ scope: StoreScope;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ lastEditedBy: string | null;
15
+ }
16
+ export type SecretMetadata = StoreMetadata;
17
+ export interface VariablePreview extends StoreMetadata {
18
+ value: string;
19
+ valueState: 'preview';
20
+ valueTruncated: boolean;
21
+ }
22
+ export interface Variable extends StoreMetadata {
23
+ value: string;
24
+ valueState: 'full';
25
+ }
26
+ export interface StoreWriteWarning {
27
+ code: 'short-secret-value' | 'sensitive-variable-name';
28
+ key: string;
29
+ }
30
+ export interface PutStoreCommand {
31
+ workspaceId: string;
32
+ key: string;
33
+ value: string;
34
+ scope: StoreScope;
35
+ }
36
+ export type PutSecretCommand = PutStoreCommand;
37
+ export type PutVariableCommand = PutStoreCommand;
38
+ export interface DeleteStoreCommand {
39
+ workspaceId: string;
40
+ key: string;
41
+ scope: StoreScope;
42
+ }
43
+ export declare function normalizeStoreKey(key: string): string;
44
+ export declare function validateStoreKey(key: string): string | undefined;
45
+ export declare function validateNewStoreKey(key: string, params: {
46
+ mode: 'create' | 'edit';
47
+ reservedKeys: readonly string[];
48
+ kind: 'secret' | 'variable';
49
+ }): string | undefined;
50
+ export declare function shouldWarnShortSecretValue(value: string, threshold: number): boolean;
51
+ export declare function shouldWarnSensitiveVariableName(key: string): boolean;
52
+ export declare function projectIdFromScope(scope: StoreScope): string | undefined;
53
+ export declare function scopeFromProjectId(projectId: string | null): StoreScope;
54
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,uFAC2D,CAAC;AAOvF,MAAM,MAAM,UAAU,GAAG;IAAC,IAAI,EAAE,WAAW,CAAA;CAAC,GAAG;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,CAAC;AAEpF,eAAO,MAAM,mBAAmB,EAAE,UAAgC,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,SAAS,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,QAAS,SAAQ,aAAa;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,oBAAoB,GAAG,yBAAyB,CAAC;IACvD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAC;AAC/C,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIhE;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE;IAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;CAAC,GAC9F,MAAM,GAAG,SAAS,CAOpB;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEpF;AAED,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,CAEvE"}
@@ -0,0 +1,44 @@
1
+ export const STORE_KEY_HELP = 'Uppercase letters, digits and underscores; must start with a letter or underscore.';
2
+ // This policy intentionally lives here so the package core stays independent of transport DTOs.
3
+ const STORE_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
4
+ const STORE_KEY_MAX_LENGTH = 128;
5
+ const SENSITIVE_NAME_PATTERNS = [
6
+ 'TOKEN',
7
+ 'SECRET',
8
+ 'PASSWORD',
9
+ 'KEY'
10
+ ];
11
+ export const workspaceStoreScope = {
12
+ kind: 'workspace'
13
+ };
14
+ export function normalizeStoreKey(key) {
15
+ return key.toUpperCase();
16
+ }
17
+ export function validateStoreKey(key) {
18
+ return key.length > 0 && key.length <= STORE_KEY_MAX_LENGTH && STORE_KEY_PATTERN.test(key) ? undefined : STORE_KEY_HELP;
19
+ }
20
+ export function validateNewStoreKey(key, params) {
21
+ const formatError = validateStoreKey(key);
22
+ if (formatError) return formatError;
23
+ if (params.mode === 'create' && params.reservedKeys.includes(key)) {
24
+ return `A ${params.kind} with this name already exists. Edit it instead.`;
25
+ }
26
+ return undefined;
27
+ }
28
+ export function shouldWarnShortSecretValue(value, threshold) {
29
+ return value.length > 0 && value.length < threshold;
30
+ }
31
+ export function shouldWarnSensitiveVariableName(key) {
32
+ return SENSITIVE_NAME_PATTERNS.some((pattern)=>key.includes(pattern));
33
+ }
34
+ export function projectIdFromScope(scope) {
35
+ return scope.kind === 'project' ? scope.projectId : undefined;
36
+ }
37
+ export function scopeFromProjectId(projectId) {
38
+ return projectId ? {
39
+ kind: 'project',
40
+ projectId
41
+ } : workspaceStoreScope;
42
+ }
43
+
44
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/store.ts"],"sourcesContent":["export const STORE_KEY_HELP =\n 'Uppercase letters, digits and underscores; must start with a letter or underscore.';\n\n// This policy intentionally lives here so the package core stays independent of transport DTOs.\nconst STORE_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/;\nconst STORE_KEY_MAX_LENGTH = 128;\nconst SENSITIVE_NAME_PATTERNS = ['TOKEN', 'SECRET', 'PASSWORD', 'KEY'];\n\nexport type StoreScope = {kind: 'workspace'} | {kind: 'project'; projectId: string};\n\nexport const workspaceStoreScope: StoreScope = {kind: 'workspace'};\n\nexport interface StoreMetadata {\n key: string;\n scope: StoreScope;\n createdAt: string;\n updatedAt: string;\n lastEditedBy: string | null;\n}\n\nexport type SecretMetadata = StoreMetadata;\n\nexport interface VariablePreview extends StoreMetadata {\n value: string;\n valueState: 'preview';\n valueTruncated: boolean;\n}\n\nexport interface Variable extends StoreMetadata {\n value: string;\n valueState: 'full';\n}\n\nexport interface StoreWriteWarning {\n code: 'short-secret-value' | 'sensitive-variable-name';\n key: string;\n}\n\nexport interface PutStoreCommand {\n workspaceId: string;\n key: string;\n value: string;\n scope: StoreScope;\n}\n\nexport type PutSecretCommand = PutStoreCommand;\nexport type PutVariableCommand = PutStoreCommand;\n\nexport interface DeleteStoreCommand {\n workspaceId: string;\n key: string;\n scope: StoreScope;\n}\n\nexport function normalizeStoreKey(key: string): string {\n return key.toUpperCase();\n}\n\nexport function validateStoreKey(key: string): string | undefined {\n return key.length > 0 && key.length <= STORE_KEY_MAX_LENGTH && STORE_KEY_PATTERN.test(key)\n ? undefined\n : STORE_KEY_HELP;\n}\n\nexport function validateNewStoreKey(\n key: string,\n params: {mode: 'create' | 'edit'; reservedKeys: readonly string[]; kind: 'secret' | 'variable'},\n): string | undefined {\n const formatError = validateStoreKey(key);\n if (formatError) return formatError;\n if (params.mode === 'create' && params.reservedKeys.includes(key)) {\n return `A ${params.kind} with this name already exists. Edit it instead.`;\n }\n return undefined;\n}\n\nexport function shouldWarnShortSecretValue(value: string, threshold: number): boolean {\n return value.length > 0 && value.length < threshold;\n}\n\nexport function shouldWarnSensitiveVariableName(key: string): boolean {\n return SENSITIVE_NAME_PATTERNS.some((pattern) => key.includes(pattern));\n}\n\nexport function projectIdFromScope(scope: StoreScope): string | undefined {\n return scope.kind === 'project' ? scope.projectId : undefined;\n}\n\nexport function scopeFromProjectId(projectId: string | null): StoreScope {\n return projectId ? {kind: 'project', projectId} : workspaceStoreScope;\n}\n"],"names":["STORE_KEY_HELP","STORE_KEY_PATTERN","STORE_KEY_MAX_LENGTH","SENSITIVE_NAME_PATTERNS","workspaceStoreScope","kind","normalizeStoreKey","key","toUpperCase","validateStoreKey","length","test","undefined","validateNewStoreKey","params","formatError","mode","reservedKeys","includes","shouldWarnShortSecretValue","value","threshold","shouldWarnSensitiveVariableName","some","pattern","projectIdFromScope","scope","projectId","scopeFromProjectId"],"mappings":"AAAA,OAAO,MAAMA,iBACX,qFAAqF;AAEvF,gGAAgG;AAChG,MAAMC,oBAAoB;AAC1B,MAAMC,uBAAuB;AAC7B,MAAMC,0BAA0B;IAAC;IAAS;IAAU;IAAY;CAAM;AAItE,OAAO,MAAMC,sBAAkC;IAACC,MAAM;AAAW,EAAE;AA4CnE,OAAO,SAASC,kBAAkBC,GAAW;IAC3C,OAAOA,IAAIC,WAAW;AACxB;AAEA,OAAO,SAASC,iBAAiBF,GAAW;IAC1C,OAAOA,IAAIG,MAAM,GAAG,KAAKH,IAAIG,MAAM,IAAIR,wBAAwBD,kBAAkBU,IAAI,CAACJ,OAClFK,YACAZ;AACN;AAEA,OAAO,SAASa,oBACdN,GAAW,EACXO,MAA+F;IAE/F,MAAMC,cAAcN,iBAAiBF;IACrC,IAAIQ,aAAa,OAAOA;IACxB,IAAID,OAAOE,IAAI,KAAK,YAAYF,OAAOG,YAAY,CAACC,QAAQ,CAACX,MAAM;QACjE,OAAO,CAAC,EAAE,EAAEO,OAAOT,IAAI,CAAC,gDAAgD,CAAC;IAC3E;IACA,OAAOO;AACT;AAEA,OAAO,SAASO,2BAA2BC,KAAa,EAAEC,SAAiB;IACzE,OAAOD,MAAMV,MAAM,GAAG,KAAKU,MAAMV,MAAM,GAAGW;AAC5C;AAEA,OAAO,SAASC,gCAAgCf,GAAW;IACzD,OAAOJ,wBAAwBoB,IAAI,CAAC,CAACC,UAAYjB,IAAIW,QAAQ,CAACM;AAChE;AAEA,OAAO,SAASC,mBAAmBC,KAAiB;IAClD,OAAOA,MAAMrB,IAAI,KAAK,YAAYqB,MAAMC,SAAS,GAAGf;AACtD;AAEA,OAAO,SAASgB,mBAAmBD,SAAwB;IACzD,OAAOA,YAAY;QAACtB,MAAM;QAAWsB;IAAS,IAAIvB;AACpD"}
@@ -0,0 +1,39 @@
1
+ export declare const secretsSettingsSections: readonly [{
2
+ readonly id: "settings.secrets";
3
+ readonly pathSegment: "secrets";
4
+ readonly label: "Secrets";
5
+ readonly icon: "keyLine";
6
+ readonly order: 500;
7
+ }, {
8
+ readonly id: "settings.variables";
9
+ readonly pathSegment: "variables";
10
+ readonly label: "Variables";
11
+ readonly icon: "bracesLine";
12
+ readonly order: 600;
13
+ }];
14
+ export declare const secretsFeature: {
15
+ readonly id: "shipfox.secrets";
16
+ readonly routes: readonly [{
17
+ readonly path: "/workspaces/$wid/settings/secrets";
18
+ readonly parent: "workspaceSettings";
19
+ readonly impl: "@shipfox/client-secrets/routes/secrets-settings";
20
+ }, {
21
+ readonly path: "/workspaces/$wid/settings/variables";
22
+ readonly parent: "workspaceSettings";
23
+ readonly impl: "@shipfox/client-secrets/routes/variables-settings";
24
+ }];
25
+ readonly settingsSections: readonly [{
26
+ readonly id: "settings.secrets";
27
+ readonly pathSegment: "secrets";
28
+ readonly label: "Secrets";
29
+ readonly icon: "keyLine";
30
+ readonly order: 500;
31
+ }, {
32
+ readonly id: "settings.variables";
33
+ readonly pathSegment: "variables";
34
+ readonly label: "Variables";
35
+ readonly icon: "bracesLine";
36
+ readonly order: 600;
37
+ }];
38
+ };
39
+ //# sourceMappingURL=feature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../src/feature.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,uBAAuB;;;;;;;;;;;;EASgB,CAAC;AAErD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;CAezB,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { defineClientFeature } from '@shipfox/client-shell';
2
+ export const secretsSettingsSections = [
3
+ {
4
+ id: 'settings.secrets',
5
+ pathSegment: 'secrets',
6
+ label: 'Secrets',
7
+ icon: 'keyLine',
8
+ order: 500
9
+ },
10
+ {
11
+ id: 'settings.variables',
12
+ pathSegment: 'variables',
13
+ label: 'Variables',
14
+ icon: 'bracesLine',
15
+ order: 600
16
+ }
17
+ ];
18
+ export const secretsFeature = defineClientFeature({
19
+ id: 'shipfox.secrets',
20
+ routes: [
21
+ {
22
+ path: '/workspaces/$wid/settings/secrets',
23
+ parent: 'workspaceSettings',
24
+ impl: '@shipfox/client-secrets/routes/secrets-settings'
25
+ },
26
+ {
27
+ path: '/workspaces/$wid/settings/variables',
28
+ parent: 'workspaceSettings',
29
+ impl: '@shipfox/client-secrets/routes/variables-settings'
30
+ }
31
+ ],
32
+ settingsSections: secretsSettingsSections
33
+ });
34
+
35
+ //# sourceMappingURL=feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/feature.ts"],"sourcesContent":["import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';\n\nexport const secretsSettingsSections = [\n {id: 'settings.secrets', pathSegment: 'secrets', label: 'Secrets', icon: 'keyLine', order: 500},\n {\n id: 'settings.variables',\n pathSegment: 'variables',\n label: 'Variables',\n icon: 'bracesLine',\n order: 600,\n },\n] as const satisfies readonly SettingsSectionEntry[];\n\nexport const secretsFeature = defineClientFeature({\n id: 'shipfox.secrets',\n routes: [\n {\n path: '/workspaces/$wid/settings/secrets',\n parent: 'workspaceSettings',\n impl: '@shipfox/client-secrets/routes/secrets-settings',\n },\n {\n path: '/workspaces/$wid/settings/variables',\n parent: 'workspaceSettings',\n impl: '@shipfox/client-secrets/routes/variables-settings',\n },\n ],\n settingsSections: secretsSettingsSections,\n});\n"],"names":["defineClientFeature","secretsSettingsSections","id","pathSegment","label","icon","order","secretsFeature","routes","path","parent","impl","settingsSections"],"mappings":"AAAA,SAAQA,mBAAmB,QAAkC,wBAAwB;AAErF,OAAO,MAAMC,0BAA0B;IACrC;QAACC,IAAI;QAAoBC,aAAa;QAAWC,OAAO;QAAWC,MAAM;QAAWC,OAAO;IAAG;IAC9F;QACEJ,IAAI;QACJC,aAAa;QACbC,OAAO;QACPC,MAAM;QACNC,OAAO;IACT;CACD,CAAoD;AAErD,OAAO,MAAMC,iBAAiBP,oBAAoB;IAChDE,IAAI;IACJM,QAAQ;QACN;YACEC,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;QACA;YACEF,MAAM;YACNC,QAAQ;YACRC,MAAM;QACR;KACD;IACDC,kBAAkBX;AACpB,GAAG"}
@@ -1,56 +1,38 @@
1
- import { type SecretWriteWarningDto } from '@shipfox/api-secrets-dto';
2
- /**
3
- * Secrets and variables share an identical management surface (list / put /
4
- * delete under `/workspaces/:id/{resource}`); only the response envelope keys
5
- * and DTO types differ. This factory builds the typed transport functions,
6
- * query keys, and React Query hooks for one resource so `secrets.ts` and
7
- * `variables.ts` stay thin instantiations. The forms and sections diverge
8
- * (write-only vs readable) and are kept separate on purpose.
9
- */
10
- export interface CreateStoreApiConfig<TItem, TListResponse, TPutResponse> {
11
- /** URL segment and query-key namespace, e.g. `'secrets'` or `'variables'`. */
12
- resource: string;
13
- /** Pulls the item array out of a list response. */
14
- listItems: (response: TListResponse) => TItem[];
15
- /** Pulls the single item out of a put response. */
16
- putItem: (response: TPutResponse) => TItem;
17
- }
18
- export interface PutResult<TItem> {
19
- item: TItem;
20
- warnings: SecretWriteWarningDto[];
21
- }
22
- export declare function createStoreApi<TItem, TListResponse, TPutBody, TPutResponse extends {
23
- warnings: SecretWriteWarningDto[];
24
- }>(config: CreateStoreApiConfig<TItem, TListResponse, TPutResponse>): {
1
+ import { type StandardSchema, type StandardSchemaOutput } from '@shipfox/client-api';
2
+ import { type FetchQueryOptions } from '@tanstack/react-query';
3
+ import { type DeleteStoreCommand, type PutStoreCommand, type StoreScope, type StoreWriteWarning } from '#core/store.js';
4
+ type StoreApiConfig<TListItem, TPutItem, TListSchema extends StandardSchema, TPutSchema extends StandardSchema> = {
5
+ resource: 'secrets' | 'variables';
6
+ listResponseSchema: TListSchema;
7
+ putResponseSchema: TPutSchema;
8
+ toListItems: (response: StandardSchemaOutput<TListSchema>) => TListItem[];
9
+ toPutResult: (response: StandardSchemaOutput<TPutSchema>) => {
10
+ item: TPutItem;
11
+ warnings: StoreWriteWarning[];
12
+ };
13
+ };
14
+ export declare function createStoreApi<TListItem, TPutItem, TListSchema extends StandardSchema, TPutSchema extends StandardSchema>(config: StoreApiConfig<TListItem, TPutItem, TListSchema, TPutSchema>): {
25
15
  queryKeys: {
26
- all: readonly [string];
27
- list: (workspaceId: string) => readonly [string, "list", string];
16
+ all: readonly ["secrets" | "variables"];
17
+ list: (workspaceId: string, scope?: StoreScope) => readonly ["secrets" | "variables", "list", string, StoreScope];
28
18
  };
29
19
  listAll: (params: {
30
20
  workspaceId: string;
31
- projectId?: string | undefined;
21
+ scope?: StoreScope;
32
22
  signal?: AbortSignal | undefined;
33
- }) => Promise<TItem[]>;
34
- put: (params: {
35
- workspaceId: string;
36
- key: string;
37
- body: TPutBody;
38
- }) => Promise<PutResult<TItem>>;
39
- remove: (params: {
40
- workspaceId: string;
41
- key: string;
42
- projectId?: string | undefined;
43
- }) => Promise<void>;
44
- useListQuery: (workspaceId: string | undefined) => import("@tanstack/react-query").UseQueryResult<NoInfer<TItem[]>, Error>;
45
- usePutMutation: () => import("@tanstack/react-query").UseMutationResult<PutResult<TItem>, Error, {
46
- workspaceId: string;
47
- key: string;
48
- body: TPutBody;
49
- }, unknown>;
50
- useDeleteMutation: () => import("@tanstack/react-query").UseMutationResult<void, Error, {
51
- workspaceId: string;
52
- key: string;
53
- projectId?: string | undefined;
54
- }, unknown>;
23
+ }) => Promise<TListItem[]>;
24
+ put: (command: PutStoreCommand) => Promise<{
25
+ item: TPutItem;
26
+ warnings: StoreWriteWarning[];
27
+ }>;
28
+ remove: (command: DeleteStoreCommand) => Promise<void>;
29
+ listQueryOptions: (workspaceId: string, scope?: StoreScope) => FetchQueryOptions<TListItem[], Error, TListItem[], readonly ["secrets" | "variables", "list", string, StoreScope], never>;
30
+ useListQuery: (workspaceId: string | undefined, scope?: StoreScope) => import("@tanstack/react-query").UseQueryResult<NoInfer<TListItem[]>, Error>;
31
+ usePutMutation: () => import("@tanstack/react-query").UseMutationResult<{
32
+ item: TPutItem;
33
+ warnings: StoreWriteWarning[];
34
+ }, Error, PutStoreCommand, unknown>;
35
+ useDeleteMutation: () => import("@tanstack/react-query").UseMutationResult<void, Error, DeleteStoreCommand, unknown>;
55
36
  };
37
+ export {};
56
38
  //# sourceMappingURL=create-store-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-store-api.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/create-store-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,qBAAqB,EAAC,MAAM,0BAA0B,CAAC;AAI5F;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY;IACtE,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,SAAS,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,KAAK,EAAE,CAAC;IAChD,mDAAmD;IACnD,OAAO,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,KAAK,CAAC;CAC5C;AAED,MAAM,WAAW,SAAS,CAAC,KAAK;IAC9B,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,EAAE,qBAAqB,EAAE,CAAC;CACnC;AAED,wBAAgB,cAAc,CAC5B,KAAK,EACL,aAAa,EACb,QAAQ,EACR,YAAY,SAAS;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAC,EACxD,MAAM,EAAE,oBAAoB,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY,CAAC;;;4BAK1C,MAAM;;sBAMG;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;KAClC,KAAG,OAAO,CAAC,KAAK,EAAE,CAAC;kBAUO;QACzB,WAAW,EAAE,MAAM,CAAC;QACpB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,QAAQ,CAAC;KAChB,KAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;qBAQC;QAC5B,WAAW,EAAE,MAAM,CAAC;QACpB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAChC,KAAG,OAAO,CAAC,IAAI,CAAC;gCAUkB,MAAM,GAAG,SAAS;;qBAzBtC,MAAM;aACd,MAAM;cACL,QAAQ;;;qBAUD,MAAM;aACd,MAAM;oBACC,MAAM,GAAG,SAAS;;EA0CjC"}
1
+ {"version":3,"file":"create-store-api.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/create-store-api.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,iBAAiB,EAKvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAEpB,KAAK,UAAU,EACf,KAAK,iBAAiB,EAEvB,MAAM,gBAAgB,CAAC;AAExB,KAAK,cAAc,CACjB,SAAS,EACT,QAAQ,EACR,WAAW,SAAS,cAAc,EAClC,UAAU,SAAS,cAAc,IAC/B;IACF,QAAQ,EAAE,SAAS,GAAG,WAAW,CAAC;IAClC,kBAAkB,EAAE,WAAW,CAAC;IAChC,iBAAiB,EAAE,UAAU,CAAC;IAC9B,WAAW,EAAE,CAAC,QAAQ,EAAE,oBAAoB,CAAC,WAAW,CAAC,KAAK,SAAS,EAAE,CAAC;IAC1E,WAAW,EAAE,CAAC,QAAQ,EAAE,oBAAoB,CAAC,UAAU,CAAC,KAAK;QAC3D,IAAI,EAAE,QAAQ,CAAC;QACf,QAAQ,EAAE,iBAAiB,EAAE,CAAC;KAC/B,CAAC;CACH,CAAC;AAEF,wBAAgB,cAAc,CAC5B,SAAS,EACT,QAAQ,EACR,WAAW,SAAS,cAAc,EAClC,UAAU,SAAS,cAAc,EACjC,MAAM,EAAE,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,CAAC;;;4BAI9C,MAAM,UAAS,UAAU;;sBAmBhB;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;KAClC,KAAG,OAAO,CAAC,SAAS,EAAE,CAAC;mBAUI,eAAe;;kBA9C/B,iBAAiB,EAAE;;sBA0DA,kBAAkB,KAAG,OAAO,CAAC,IAAI,CAAC;oCAUlD,MAAM,UACZ,UAAU;gCAQgB,MAAM,GAAG,SAAS,UAAS,UAAU;;;kBA7E5D,iBAAiB,EAAE;;;EA2GhC"}
@@ -1,84 +1,82 @@
1
1
  import { SECRETS_MAX_LIST_LIMIT } from '@shipfox/api-secrets-dto';
2
- import { apiRequest } from '@shipfox/client-api';
3
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ import { checkedApiRequest, emptyResponseSchema } from '@shipfox/client-api';
3
+ import { queryOptions, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
+ import { projectIdFromScope, workspaceStoreScope } from '#core/store.js';
4
5
  export function createStoreApi(config) {
5
- const { resource, listItems, putItem } = config;
6
+ const { resource } = config;
6
7
  const queryKeys = {
7
8
  all: [
8
9
  resource
9
10
  ],
10
- list: (workspaceId)=>[
11
- resource,
11
+ list: (workspaceId, scope = workspaceStoreScope)=>[
12
+ ...queryKeys.all,
12
13
  'list',
13
- workspaceId
14
+ workspaceId,
15
+ scope
14
16
  ]
15
17
  };
16
- // The store is a bounded set (per-workspace cap), so the UI fetches every key
17
- // in one call rather than paginating: request the max limit and ignore the
18
- // cursor. See SECRETS_MAX_LIST_LIMIT / the S1a list route.
18
+ function searchForScope(scope, includeLimit = false) {
19
+ const search = new URLSearchParams();
20
+ if (includeLimit) search.set('limit', String(SECRETS_MAX_LIST_LIMIT));
21
+ const projectId = projectIdFromScope(scope);
22
+ if (projectId) search.set('project_id', projectId);
23
+ return search.toString();
24
+ }
19
25
  async function listAll(params) {
20
- const search = new URLSearchParams({
21
- limit: String(SECRETS_MAX_LIST_LIMIT)
22
- });
23
- if (params.projectId) search.set('project_id', params.projectId);
24
- const response = await apiRequest(`/workspaces/${params.workspaceId}/${resource}?${search.toString()}`, {
26
+ const scope = params.scope ?? workspaceStoreScope;
27
+ const response = await checkedApiRequest(config.listResponseSchema, `/workspaces/${params.workspaceId}/${resource}?${searchForScope(scope, true)}`, {
25
28
  signal: params.signal
26
29
  });
27
- return listItems(response);
30
+ return config.toListItems(response);
28
31
  }
29
- async function put(params) {
30
- const response = await apiRequest(`/workspaces/${params.workspaceId}/${resource}/${encodeURIComponent(params.key)}`, {
32
+ async function put(command) {
33
+ const response = await checkedApiRequest(config.putResponseSchema, `/workspaces/${command.workspaceId}/${resource}/${encodeURIComponent(command.key)}`, {
31
34
  method: 'PUT',
32
- body: params.body
35
+ body: {
36
+ value: command.value,
37
+ project_id: projectIdFromScope(command.scope)
38
+ }
33
39
  });
34
- return {
35
- item: putItem(response),
36
- warnings: response.warnings
37
- };
40
+ return config.toPutResult(response);
38
41
  }
39
- async function remove(params) {
40
- const search = params.projectId ? `?${new URLSearchParams({
41
- project_id: params.projectId
42
- }).toString()}` : '';
43
- await apiRequest(`/workspaces/${params.workspaceId}/${resource}/${encodeURIComponent(params.key)}${search}`, {
42
+ async function remove(command) {
43
+ const search = searchForScope(command.scope);
44
+ await checkedApiRequest(emptyResponseSchema, `/workspaces/${command.workspaceId}/${resource}/${encodeURIComponent(command.key)}${search ? `?${search}` : ''}`, {
44
45
  method: 'DELETE'
45
46
  });
46
47
  }
47
- function useListQuery(workspaceId) {
48
- return useQuery({
49
- queryKey: workspaceId ? queryKeys.list(workspaceId) : [
50
- resource,
51
- 'list'
52
- ],
53
- enabled: Boolean(workspaceId),
48
+ function listQueryOptions(workspaceId, scope = workspaceStoreScope) {
49
+ return queryOptions({
50
+ queryKey: queryKeys.list(workspaceId, scope),
54
51
  queryFn: ({ signal })=>listAll({
55
- workspaceId: workspaceId ?? '',
52
+ workspaceId,
53
+ scope,
56
54
  signal
57
55
  })
58
56
  });
59
57
  }
58
+ function useListQuery(workspaceId, scope = workspaceStoreScope) {
59
+ return useQuery({
60
+ ...listQueryOptions(workspaceId ?? '', scope),
61
+ enabled: Boolean(workspaceId)
62
+ });
63
+ }
60
64
  function usePutMutation() {
61
65
  const queryClient = useQueryClient();
62
66
  return useMutation({
63
67
  mutationFn: put,
64
- // Invalidate the whole resource namespace so the per-item detail cache
65
- // (full variable values) is refreshed too, not just the list.
66
- onSuccess: async ()=>{
67
- await queryClient.invalidateQueries({
68
+ onSuccess: async ()=>await queryClient.invalidateQueries({
68
69
  queryKey: queryKeys.all
69
- });
70
- }
70
+ })
71
71
  });
72
72
  }
73
73
  function useDeleteMutation() {
74
74
  const queryClient = useQueryClient();
75
75
  return useMutation({
76
76
  mutationFn: remove,
77
- onSuccess: async ()=>{
78
- await queryClient.invalidateQueries({
77
+ onSuccess: async ()=>await queryClient.invalidateQueries({
79
78
  queryKey: queryKeys.all
80
- });
81
- }
79
+ })
82
80
  });
83
81
  }
84
82
  return {
@@ -86,6 +84,7 @@ export function createStoreApi(config) {
86
84
  listAll,
87
85
  put,
88
86
  remove,
87
+ listQueryOptions,
89
88
  useListQuery,
90
89
  usePutMutation,
91
90
  useDeleteMutation