@oxyhq/core 18.0.0 → 19.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/i18n/locales/en-US.json +49 -5
- package/dist/cjs/i18n/locales/es-ES.json +49 -5
- package/dist/cjs/i18n/locales/locales/en-US.json +49 -5
- package/dist/cjs/i18n/locales/locales/es-ES.json +49 -5
- package/dist/cjs/index.js +17 -6
- package/dist/cjs/mixins/OxyServices.accounts.js +69 -31
- package/dist/cjs/mixins/OxyServices.followGraph.js +204 -0
- package/dist/cjs/mixins/OxyServices.user.js +17 -20
- package/dist/cjs/mixins/index.js +4 -0
- package/dist/cjs/server/index.js +8 -2
- package/dist/cjs/server/userInvalidation.js +6 -28
- package/dist/cjs/session/accountProjection.js +45 -9
- package/dist/cjs/utils/accountCacheSweep.js +80 -0
- package/dist/cjs/utils/identityCacheSweep.js +97 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/i18n/locales/en-US.json +49 -5
- package/dist/esm/i18n/locales/es-ES.json +49 -5
- package/dist/esm/i18n/locales/locales/en-US.json +49 -5
- package/dist/esm/i18n/locales/locales/es-ES.json +49 -5
- package/dist/esm/index.js +8 -2
- package/dist/esm/mixins/OxyServices.accounts.js +64 -30
- package/dist/esm/mixins/OxyServices.followGraph.js +201 -0
- package/dist/esm/mixins/OxyServices.user.js +17 -20
- package/dist/esm/mixins/index.js +4 -0
- package/dist/esm/server/index.js +5 -1
- package/dist/esm/server/userInvalidation.js +5 -26
- package/dist/esm/session/accountProjection.js +44 -9
- package/dist/esm/utils/accountCacheSweep.js +75 -0
- package/dist/esm/utils/identityCacheSweep.js +92 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/mixins/OxyServices.accounts.d.ts +91 -34
- package/dist/types/mixins/OxyServices.followGraph.d.ts +211 -0
- package/dist/types/mixins/OxyServices.user.d.ts +10 -7
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/interfaces.d.ts +11 -3
- package/dist/types/server/index.d.ts +4 -2
- package/dist/types/server/userInvalidation.d.ts +5 -24
- package/dist/types/session/accountProjection.d.ts +38 -4
- package/dist/types/utils/accountCacheSweep.d.ts +75 -0
- package/dist/types/utils/identityCacheSweep.d.ts +80 -0
- package/package.json +2 -2
- package/src/i18n/locales/en-US.json +49 -5
- package/src/i18n/locales/es-ES.json +49 -5
- package/src/index.ts +15 -2
- package/src/mixins/OxyServices.accounts.ts +123 -45
- package/src/mixins/OxyServices.followGraph.ts +266 -0
- package/src/mixins/OxyServices.user.ts +17 -20
- package/src/mixins/__tests__/accounts.test.ts +5 -0
- package/src/mixins/__tests__/followGraph.test.ts +128 -0
- package/src/mixins/__tests__/identityWriteCacheInvalidation.test.ts +407 -0
- package/src/mixins/index.ts +5 -0
- package/src/models/interfaces.ts +11 -3
- package/src/server/__tests__/userInvalidation.test.ts +3 -20
- package/src/server/index.ts +5 -2
- package/src/server/userInvalidation.ts +8 -36
- package/src/session/__tests__/accountProjection.test.ts +109 -2
- package/src/session/accountProjection.ts +47 -9
- package/src/utils/__tests__/identityCacheSweep.test.ts +151 -0
- package/src/utils/accountCacheSweep.ts +93 -0
- package/src/utils/identityCacheSweep.ts +104 -0
|
@@ -1748,11 +1748,53 @@
|
|
|
1748
1748
|
"description": "A programmatic account with service credentials"
|
|
1749
1749
|
}
|
|
1750
1750
|
},
|
|
1751
|
-
"
|
|
1751
|
+
"accountCategory": {
|
|
1752
|
+
"news": "News",
|
|
1753
|
+
"politics": "Politics & policy",
|
|
1754
|
+
"business": "Business",
|
|
1755
|
+
"startup": "Startups & entrepreneurship",
|
|
1756
|
+
"finance": "Finance & investing",
|
|
1757
|
+
"crypto": "Crypto & web3",
|
|
1758
|
+
"marketplace": "Marketplace & classifieds",
|
|
1759
|
+
"retail": "Retail & ecommerce",
|
|
1760
|
+
"real_estate": "Real estate",
|
|
1752
1761
|
"agency": "Real estate agency",
|
|
1762
|
+
"landlord": "Landlord & property management",
|
|
1753
1763
|
"cooperative": "Housing cooperative",
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1764
|
+
"architecture": "Architecture & construction",
|
|
1765
|
+
"technology": "Technology",
|
|
1766
|
+
"software": "Software & development",
|
|
1767
|
+
"ai": "AI & machine learning",
|
|
1768
|
+
"security": "Security & privacy",
|
|
1769
|
+
"automation": "Automation & tools",
|
|
1770
|
+
"science": "Science & research",
|
|
1771
|
+
"education": "Education & training",
|
|
1772
|
+
"books": "Books & writing",
|
|
1773
|
+
"health": "Health & medicine",
|
|
1774
|
+
"fitness": "Fitness & wellbeing",
|
|
1775
|
+
"sports": "Sports",
|
|
1776
|
+
"gaming": "Gaming & esports",
|
|
1777
|
+
"music": "Music",
|
|
1778
|
+
"film": "Film & TV",
|
|
1779
|
+
"podcast": "Podcasts & audio",
|
|
1780
|
+
"art": "Art & design",
|
|
1781
|
+
"photography": "Photography",
|
|
1782
|
+
"comedy": "Comedy",
|
|
1783
|
+
"food": "Food & drink",
|
|
1784
|
+
"travel": "Travel & places",
|
|
1785
|
+
"fashion": "Fashion & beauty",
|
|
1786
|
+
"home_garden": "Home & garden",
|
|
1787
|
+
"diy": "DIY & making",
|
|
1788
|
+
"automotive": "Cars & motoring",
|
|
1789
|
+
"animals": "Animals & pets",
|
|
1790
|
+
"family": "Family & parenting",
|
|
1791
|
+
"nonprofit": "Non-profit & charity",
|
|
1792
|
+
"government": "Government & public sector",
|
|
1793
|
+
"community": "Local community",
|
|
1794
|
+
"activism": "Activism & advocacy",
|
|
1795
|
+
"environment": "Environment & sustainability",
|
|
1796
|
+
"religion": "Religion & spirituality",
|
|
1797
|
+
"other": "Other"
|
|
1756
1798
|
},
|
|
1757
1799
|
"create": {
|
|
1758
1800
|
"title": "Create account",
|
|
@@ -1772,8 +1814,9 @@
|
|
|
1772
1814
|
"invalidChars": "Only letters, numbers, hyphens, and underscores",
|
|
1773
1815
|
"checkFailed": "Could not check availability"
|
|
1774
1816
|
},
|
|
1775
|
-
"
|
|
1776
|
-
"label": "
|
|
1817
|
+
"accountCategory": {
|
|
1818
|
+
"label": "Categories",
|
|
1819
|
+
"hint": "Pick up to {{max}}. The first one is your main category."
|
|
1777
1820
|
},
|
|
1778
1821
|
"toasts": {
|
|
1779
1822
|
"success": "Account created",
|
|
@@ -1796,6 +1839,7 @@
|
|
|
1796
1839
|
"title": "Members",
|
|
1797
1840
|
"subtitle": "People with access to this account.",
|
|
1798
1841
|
"empty": "No members yet.",
|
|
1842
|
+
"inherited": "Inherited",
|
|
1799
1843
|
"actions": {
|
|
1800
1844
|
"remove": "Remove member",
|
|
1801
1845
|
"transfer": "Transfer ownership"
|
|
@@ -1748,11 +1748,53 @@
|
|
|
1748
1748
|
"description": "Una cuenta programática con credenciales de servicio"
|
|
1749
1749
|
}
|
|
1750
1750
|
},
|
|
1751
|
-
"
|
|
1751
|
+
"accountCategory": {
|
|
1752
|
+
"news": "Noticias",
|
|
1753
|
+
"politics": "Política",
|
|
1754
|
+
"business": "Negocios",
|
|
1755
|
+
"startup": "Startups y emprendimiento",
|
|
1756
|
+
"finance": "Finanzas e inversión",
|
|
1757
|
+
"crypto": "Cripto y web3",
|
|
1758
|
+
"marketplace": "Marketplace y anuncios",
|
|
1759
|
+
"retail": "Comercio y ecommerce",
|
|
1760
|
+
"real_estate": "Inmobiliaria",
|
|
1752
1761
|
"agency": "Agencia inmobiliaria",
|
|
1762
|
+
"landlord": "Casero y gestión de fincas",
|
|
1753
1763
|
"cooperative": "Cooperativa de vivienda",
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1764
|
+
"architecture": "Arquitectura y construcción",
|
|
1765
|
+
"technology": "Tecnología",
|
|
1766
|
+
"software": "Software y desarrollo",
|
|
1767
|
+
"ai": "IA y aprendizaje automático",
|
|
1768
|
+
"security": "Seguridad y privacidad",
|
|
1769
|
+
"automation": "Automatización y herramientas",
|
|
1770
|
+
"science": "Ciencia e investigación",
|
|
1771
|
+
"education": "Educación y formación",
|
|
1772
|
+
"books": "Libros y escritura",
|
|
1773
|
+
"health": "Salud y medicina",
|
|
1774
|
+
"fitness": "Deporte y bienestar",
|
|
1775
|
+
"sports": "Deportes",
|
|
1776
|
+
"gaming": "Videojuegos y esports",
|
|
1777
|
+
"music": "Música",
|
|
1778
|
+
"film": "Cine y TV",
|
|
1779
|
+
"podcast": "Pódcast y audio",
|
|
1780
|
+
"art": "Arte y diseño",
|
|
1781
|
+
"photography": "Fotografía",
|
|
1782
|
+
"comedy": "Comedia",
|
|
1783
|
+
"food": "Comida y bebida",
|
|
1784
|
+
"travel": "Viajes y lugares",
|
|
1785
|
+
"fashion": "Moda y belleza",
|
|
1786
|
+
"home_garden": "Hogar y jardín",
|
|
1787
|
+
"diy": "Bricolaje y manualidades",
|
|
1788
|
+
"automotive": "Coches y motor",
|
|
1789
|
+
"animals": "Animales y mascotas",
|
|
1790
|
+
"family": "Familia y crianza",
|
|
1791
|
+
"nonprofit": "ONG y benéficas",
|
|
1792
|
+
"government": "Gobierno y sector público",
|
|
1793
|
+
"community": "Comunidad local",
|
|
1794
|
+
"activism": "Activismo y defensa",
|
|
1795
|
+
"environment": "Medio ambiente y sostenibilidad",
|
|
1796
|
+
"religion": "Religión y espiritualidad",
|
|
1797
|
+
"other": "Otra"
|
|
1756
1798
|
},
|
|
1757
1799
|
"create": {
|
|
1758
1800
|
"title": "Crear cuenta",
|
|
@@ -1772,8 +1814,9 @@
|
|
|
1772
1814
|
"invalidChars": "Solo letras, números, guiones y guiones bajos",
|
|
1773
1815
|
"checkFailed": "No se pudo comprobar la disponibilidad"
|
|
1774
1816
|
},
|
|
1775
|
-
"
|
|
1776
|
-
"label": "
|
|
1817
|
+
"accountCategory": {
|
|
1818
|
+
"label": "Categorías",
|
|
1819
|
+
"hint": "Elige hasta {{max}}. La primera es tu categoría principal."
|
|
1777
1820
|
},
|
|
1778
1821
|
"toasts": {
|
|
1779
1822
|
"success": "Cuenta creada",
|
|
@@ -1796,6 +1839,7 @@
|
|
|
1796
1839
|
"title": "Miembros",
|
|
1797
1840
|
"subtitle": "Personas con acceso a esta cuenta.",
|
|
1798
1841
|
"empty": "Aún no hay miembros.",
|
|
1842
|
+
"inherited": "Heredado",
|
|
1799
1843
|
"actions": {
|
|
1800
1844
|
"remove": "Eliminar miembro",
|
|
1801
1845
|
"transfer": "Transferir propiedad"
|
|
@@ -1748,11 +1748,53 @@
|
|
|
1748
1748
|
"description": "A programmatic account with service credentials"
|
|
1749
1749
|
}
|
|
1750
1750
|
},
|
|
1751
|
-
"
|
|
1751
|
+
"accountCategory": {
|
|
1752
|
+
"news": "News",
|
|
1753
|
+
"politics": "Politics & policy",
|
|
1754
|
+
"business": "Business",
|
|
1755
|
+
"startup": "Startups & entrepreneurship",
|
|
1756
|
+
"finance": "Finance & investing",
|
|
1757
|
+
"crypto": "Crypto & web3",
|
|
1758
|
+
"marketplace": "Marketplace & classifieds",
|
|
1759
|
+
"retail": "Retail & ecommerce",
|
|
1760
|
+
"real_estate": "Real estate",
|
|
1752
1761
|
"agency": "Real estate agency",
|
|
1762
|
+
"landlord": "Landlord & property management",
|
|
1753
1763
|
"cooperative": "Housing cooperative",
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1764
|
+
"architecture": "Architecture & construction",
|
|
1765
|
+
"technology": "Technology",
|
|
1766
|
+
"software": "Software & development",
|
|
1767
|
+
"ai": "AI & machine learning",
|
|
1768
|
+
"security": "Security & privacy",
|
|
1769
|
+
"automation": "Automation & tools",
|
|
1770
|
+
"science": "Science & research",
|
|
1771
|
+
"education": "Education & training",
|
|
1772
|
+
"books": "Books & writing",
|
|
1773
|
+
"health": "Health & medicine",
|
|
1774
|
+
"fitness": "Fitness & wellbeing",
|
|
1775
|
+
"sports": "Sports",
|
|
1776
|
+
"gaming": "Gaming & esports",
|
|
1777
|
+
"music": "Music",
|
|
1778
|
+
"film": "Film & TV",
|
|
1779
|
+
"podcast": "Podcasts & audio",
|
|
1780
|
+
"art": "Art & design",
|
|
1781
|
+
"photography": "Photography",
|
|
1782
|
+
"comedy": "Comedy",
|
|
1783
|
+
"food": "Food & drink",
|
|
1784
|
+
"travel": "Travel & places",
|
|
1785
|
+
"fashion": "Fashion & beauty",
|
|
1786
|
+
"home_garden": "Home & garden",
|
|
1787
|
+
"diy": "DIY & making",
|
|
1788
|
+
"automotive": "Cars & motoring",
|
|
1789
|
+
"animals": "Animals & pets",
|
|
1790
|
+
"family": "Family & parenting",
|
|
1791
|
+
"nonprofit": "Non-profit & charity",
|
|
1792
|
+
"government": "Government & public sector",
|
|
1793
|
+
"community": "Local community",
|
|
1794
|
+
"activism": "Activism & advocacy",
|
|
1795
|
+
"environment": "Environment & sustainability",
|
|
1796
|
+
"religion": "Religion & spirituality",
|
|
1797
|
+
"other": "Other"
|
|
1756
1798
|
},
|
|
1757
1799
|
"create": {
|
|
1758
1800
|
"title": "Create account",
|
|
@@ -1772,8 +1814,9 @@
|
|
|
1772
1814
|
"invalidChars": "Only letters, numbers, hyphens, and underscores",
|
|
1773
1815
|
"checkFailed": "Could not check availability"
|
|
1774
1816
|
},
|
|
1775
|
-
"
|
|
1776
|
-
"label": "
|
|
1817
|
+
"accountCategory": {
|
|
1818
|
+
"label": "Categories",
|
|
1819
|
+
"hint": "Pick up to {{max}}. The first one is your main category."
|
|
1777
1820
|
},
|
|
1778
1821
|
"toasts": {
|
|
1779
1822
|
"success": "Account created",
|
|
@@ -1796,6 +1839,7 @@
|
|
|
1796
1839
|
"title": "Members",
|
|
1797
1840
|
"subtitle": "People with access to this account.",
|
|
1798
1841
|
"empty": "No members yet.",
|
|
1842
|
+
"inherited": "Inherited",
|
|
1799
1843
|
"actions": {
|
|
1800
1844
|
"remove": "Remove member",
|
|
1801
1845
|
"transfer": "Transfer ownership"
|
|
@@ -1748,11 +1748,53 @@
|
|
|
1748
1748
|
"description": "Una cuenta programática con credenciales de servicio"
|
|
1749
1749
|
}
|
|
1750
1750
|
},
|
|
1751
|
-
"
|
|
1751
|
+
"accountCategory": {
|
|
1752
|
+
"news": "Noticias",
|
|
1753
|
+
"politics": "Política",
|
|
1754
|
+
"business": "Negocios",
|
|
1755
|
+
"startup": "Startups y emprendimiento",
|
|
1756
|
+
"finance": "Finanzas e inversión",
|
|
1757
|
+
"crypto": "Cripto y web3",
|
|
1758
|
+
"marketplace": "Marketplace y anuncios",
|
|
1759
|
+
"retail": "Comercio y ecommerce",
|
|
1760
|
+
"real_estate": "Inmobiliaria",
|
|
1752
1761
|
"agency": "Agencia inmobiliaria",
|
|
1762
|
+
"landlord": "Casero y gestión de fincas",
|
|
1753
1763
|
"cooperative": "Cooperativa de vivienda",
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1764
|
+
"architecture": "Arquitectura y construcción",
|
|
1765
|
+
"technology": "Tecnología",
|
|
1766
|
+
"software": "Software y desarrollo",
|
|
1767
|
+
"ai": "IA y aprendizaje automático",
|
|
1768
|
+
"security": "Seguridad y privacidad",
|
|
1769
|
+
"automation": "Automatización y herramientas",
|
|
1770
|
+
"science": "Ciencia e investigación",
|
|
1771
|
+
"education": "Educación y formación",
|
|
1772
|
+
"books": "Libros y escritura",
|
|
1773
|
+
"health": "Salud y medicina",
|
|
1774
|
+
"fitness": "Deporte y bienestar",
|
|
1775
|
+
"sports": "Deportes",
|
|
1776
|
+
"gaming": "Videojuegos y esports",
|
|
1777
|
+
"music": "Música",
|
|
1778
|
+
"film": "Cine y TV",
|
|
1779
|
+
"podcast": "Pódcast y audio",
|
|
1780
|
+
"art": "Arte y diseño",
|
|
1781
|
+
"photography": "Fotografía",
|
|
1782
|
+
"comedy": "Comedia",
|
|
1783
|
+
"food": "Comida y bebida",
|
|
1784
|
+
"travel": "Viajes y lugares",
|
|
1785
|
+
"fashion": "Moda y belleza",
|
|
1786
|
+
"home_garden": "Hogar y jardín",
|
|
1787
|
+
"diy": "Bricolaje y manualidades",
|
|
1788
|
+
"automotive": "Coches y motor",
|
|
1789
|
+
"animals": "Animales y mascotas",
|
|
1790
|
+
"family": "Familia y crianza",
|
|
1791
|
+
"nonprofit": "ONG y benéficas",
|
|
1792
|
+
"government": "Gobierno y sector público",
|
|
1793
|
+
"community": "Comunidad local",
|
|
1794
|
+
"activism": "Activismo y defensa",
|
|
1795
|
+
"environment": "Medio ambiente y sostenibilidad",
|
|
1796
|
+
"religion": "Religión y espiritualidad",
|
|
1797
|
+
"other": "Otra"
|
|
1756
1798
|
},
|
|
1757
1799
|
"create": {
|
|
1758
1800
|
"title": "Crear cuenta",
|
|
@@ -1772,8 +1814,9 @@
|
|
|
1772
1814
|
"invalidChars": "Solo letras, números, guiones y guiones bajos",
|
|
1773
1815
|
"checkFailed": "No se pudo comprobar la disponibilidad"
|
|
1774
1816
|
},
|
|
1775
|
-
"
|
|
1776
|
-
"label": "
|
|
1817
|
+
"accountCategory": {
|
|
1818
|
+
"label": "Categorías",
|
|
1819
|
+
"hint": "Elige hasta {{max}}. La primera es tu categoría principal."
|
|
1777
1820
|
},
|
|
1778
1821
|
"toasts": {
|
|
1779
1822
|
"success": "Cuenta creada",
|
|
@@ -1796,6 +1839,7 @@
|
|
|
1796
1839
|
"title": "Miembros",
|
|
1797
1840
|
"subtitle": "Personas con acceso a esta cuenta.",
|
|
1798
1841
|
"empty": "Aún no hay miembros.",
|
|
1842
|
+
"inherited": "Heredado",
|
|
1799
1843
|
"actions": {
|
|
1800
1844
|
"remove": "Eliminar miembro",
|
|
1801
1845
|
"transfer": "Transferir propiedad"
|
package/dist/esm/index.js
CHANGED
|
@@ -38,7 +38,7 @@ export { OxyAppDataIdentifierError } from './mixins/OxyServices.appData.js';
|
|
|
38
38
|
export { getNormalizedUserId, normalizeUserIdentity, normalizeUserIdentityOrNull, } from './utils/userIdentity.js';
|
|
39
39
|
export { getCanonicalUserHandle, getNormalizedUserHandle, } from './utils/userHandle.js';
|
|
40
40
|
export { normalizeProfileLinks } from './utils/profileLinks.js';
|
|
41
|
-
export {
|
|
41
|
+
export { ACCOUNT_CATEGORY_IDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from './mixins/OxyServices.accounts.js';
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// Reputation (Oxy Trust: ledger, balances, disputes, rules, influence).
|
|
44
44
|
// The whole type family — the closed value sets, the two balance views and the
|
|
@@ -173,7 +173,13 @@ export { deviceStateToClientSessions, activeSessionIdOf, activeUserOf, accountId
|
|
|
173
173
|
// chooser: device sign-ins ∪ account graph, deduped by accountId). Pure +
|
|
174
174
|
// I/O-free — the caller hydrates profiles via `getUsersByIds`. Shared by
|
|
175
175
|
// `@oxyhq/services` and auth.oxy.so so the list can't diverge.
|
|
176
|
-
|
|
176
|
+
// `isSwitchTargetAccount` is the switcher's own question ("can I become this
|
|
177
|
+
// account?"), exported so a surface that renders `AccountNode`s rather than the
|
|
178
|
+
// projection — the Console's workspace switcher, the accounts app's
|
|
179
|
+
// managed-accounts rows — asks the SAME question instead of testing a kind
|
|
180
|
+
// literal. It is NOT `isActAsEligibleKind`: that one is false for `personal`
|
|
181
|
+
// too, so gating a switcher on it alone empties the list.
|
|
182
|
+
export { isSwitchTargetAccount, projectSwitchableAccounts, switchableAccountIds, } from './session/accountProjection.js';
|
|
177
183
|
// Headless controller for the unified account dialog. Framework-agnostic
|
|
178
184
|
// state machine + subscribe/getSnapshot store (bind via `useSyncExternalStore`)
|
|
179
185
|
// — sign-in is passkey (WebAuthn) or the Commons QR / shared-keychain handoff;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { normalizeUserIdentity } from '../utils/userIdentity.js';
|
|
2
|
+
import { evictOxyIdentityCache } from '../utils/identityCacheSweep.js';
|
|
3
|
+
import { evictOxyAccountForestCache, oxyAccountDetailCacheKey, OXY_ACCOUNT_PER_ACCOUNT_CACHE_PREFIX } from '../utils/accountCacheSweep.js';
|
|
2
4
|
import { CACHE_TIMES } from './mixinHelpers.js';
|
|
3
|
-
export { ACCOUNT_KINDS,
|
|
5
|
+
export { ACCOUNT_CATEGORY_IDS, ACCOUNT_KINDS, MAX_ACCOUNT_CATEGORIES, SELECTABLE_ACCOUNT_CATEGORY_IDS, isActAsEligibleKind, isSelectableAccountCategoryId, kindAcceptsAccountCategories, } from '@oxyhq/contracts';
|
|
4
6
|
export function OxyServicesAccountsMixin(Base) {
|
|
5
7
|
return class extends Base {
|
|
6
8
|
constructor(...args) {
|
|
@@ -112,7 +114,7 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
112
114
|
const res = await this.makeRequest('POST', '/accounts', data, { cache: false });
|
|
113
115
|
// A new account changes the accessible forest — bust every cached list
|
|
114
116
|
// (flat + tree) so it appears on the next `listAccounts()` read.
|
|
115
|
-
this
|
|
117
|
+
evictOxyAccountForestCache(this);
|
|
116
118
|
return res.account;
|
|
117
119
|
}
|
|
118
120
|
catch (error) {
|
|
@@ -158,6 +160,18 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
158
160
|
/**
|
|
159
161
|
* Update an account's mutable profile fields. Tree placement changes
|
|
160
162
|
* (reparenting) go through the dedicated move endpoint, not here.
|
|
163
|
+
*
|
|
164
|
+
* An account IS a user, so this write changes identity — and a profile
|
|
165
|
+
* screen never reads `/accounts/<id>`. It reads `GET /users/<id>` and
|
|
166
|
+
* `GET /profiles/username/<handle>`, both cached for 5 minutes in the
|
|
167
|
+
* CALLER'S OWN process, so busting only the account-graph keys left every
|
|
168
|
+
* profile surface serving the pre-edit avatar and name for the full TTL
|
|
169
|
+
* with a perfectly healthy server (the cross-service `oxy:user:invalidate`
|
|
170
|
+
* signal does not help: it evicts BACKEND caches, and cannot reach a cache
|
|
171
|
+
* living in a browser tab). {@link evictOxyIdentityCache} owns that key
|
|
172
|
+
* list — see its docs for why the handle-keyed entries are prefix-swept
|
|
173
|
+
* (a RENAME leaves the old handle's entry unreachable by any targeted key).
|
|
174
|
+
*
|
|
161
175
|
* @param accountId - The account's Mongo `_id`.
|
|
162
176
|
* @param data - Subset of updatable profile fields.
|
|
163
177
|
*/
|
|
@@ -166,8 +180,15 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
166
180
|
const res = await this.makeRequest('PATCH', `/accounts/${encodeURIComponent(accountId)}`, data, { cache: false });
|
|
167
181
|
// Bust the cached detail and every list (which embeds account profile
|
|
168
182
|
// data) so neither serves the pre-update snapshot.
|
|
169
|
-
this
|
|
170
|
-
this
|
|
183
|
+
evictOxyAccountForestCache(this, accountId);
|
|
184
|
+
// The parent's children list embeds this account's profile and is keyed
|
|
185
|
+
// by the PARENT id, so it is reachable only from the response node.
|
|
186
|
+
const parentAccountId = res.account?.parentAccountId;
|
|
187
|
+
if (parentAccountId) {
|
|
188
|
+
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(parentAccountId)}/children`);
|
|
189
|
+
}
|
|
190
|
+
// Every identity read of this account, whichever key it lands under.
|
|
191
|
+
evictOxyIdentityCache(this, accountId);
|
|
171
192
|
return res.account;
|
|
172
193
|
}
|
|
173
194
|
catch (error) {
|
|
@@ -184,10 +205,9 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
184
205
|
try {
|
|
185
206
|
const result = await this.makeRequest('DELETE', `/accounts/${encodeURIComponent(accountId)}`, undefined, { cache: false });
|
|
186
207
|
// Bust every cached representation of the archived account.
|
|
187
|
-
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}`);
|
|
188
208
|
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/members`);
|
|
189
209
|
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/credentials`);
|
|
190
|
-
this
|
|
210
|
+
evictOxyAccountForestCache(this, accountId);
|
|
191
211
|
return result;
|
|
192
212
|
}
|
|
193
213
|
catch (error) {
|
|
@@ -211,7 +231,27 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
211
231
|
// Account members
|
|
212
232
|
// =========================================================================
|
|
213
233
|
/**
|
|
214
|
-
* List members of an account
|
|
234
|
+
* List the members of an account: the membership rows ON it, plus the rows
|
|
235
|
+
* on its ancestors that cascade into it. Each entry carries `source`
|
|
236
|
+
* (`direct` | `inherited`) saying which it is.
|
|
237
|
+
*
|
|
238
|
+
* Inherited entries are members in every sense the server enforces — an
|
|
239
|
+
* ancestor row with `inherit: true` resolves through
|
|
240
|
+
* `resolveEffectiveAccess` and confers every account permission on the
|
|
241
|
+
* descendant, `account:act_as` included — so a roster that omitted them
|
|
242
|
+
* answered `[]` for accounts several people could act on.
|
|
243
|
+
*
|
|
244
|
+
* Two things follow for a caller. An entry's `accountId` is the account its
|
|
245
|
+
* ROW lives on, so an inherited entry names an ancestor rather than the
|
|
246
|
+
* account you asked about; and the member-mutation endpoints only accept
|
|
247
|
+
* rows on the account in the path, so gate any edit/remove/transfer
|
|
248
|
+
* affordance on `source === 'direct'`.
|
|
249
|
+
*
|
|
250
|
+
* Asking what the CALLER holds over an account is a different question, and
|
|
251
|
+
* scanning this list for yourself is the wrong way to answer it — use
|
|
252
|
+
* {@link OxyServicesAccountsMixin.getAccount}, whose `callerMembership` is
|
|
253
|
+
* the server's own resolution.
|
|
254
|
+
*
|
|
215
255
|
* @param accountId - The account's Mongo `_id`.
|
|
216
256
|
*/
|
|
217
257
|
async listAccountMembers(accountId) {
|
|
@@ -282,7 +322,7 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
282
322
|
// Ownership change alters roles in the member list AND the detail, and
|
|
283
323
|
// can change which accounts the caller "owns" in the list view.
|
|
284
324
|
this._invalidateAccountMembership(accountId);
|
|
285
|
-
this
|
|
325
|
+
evictOxyAccountForestCache(this);
|
|
286
326
|
return result;
|
|
287
327
|
}
|
|
288
328
|
catch (error) {
|
|
@@ -526,35 +566,29 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
526
566
|
// Cache-invalidation helpers
|
|
527
567
|
// =========================================================================
|
|
528
568
|
/**
|
|
529
|
-
* Bust
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
* unscoped entry plus every `?`-query variant via a prefix sweep. The prefix
|
|
534
|
-
* `GET:/accounts?` matches only the query-string list variants, never the
|
|
535
|
-
* `GET:/accounts/<id>…` detail/sub-resource keys.
|
|
569
|
+
* Bust the cached member list and detail for an account after a membership
|
|
570
|
+
* mutation. The member list (`listAccountMembers`) and the detail
|
|
571
|
+
* (`getAccount`, which can embed the caller's membership) both go stale when
|
|
572
|
+
* the member set or a member's role changes.
|
|
536
573
|
*
|
|
537
574
|
* Internal helper (leading underscore); not part of the supported public
|
|
538
575
|
* surface. Public rather than `private` because mixins compose into an
|
|
539
576
|
* exported anonymous class, where TypeScript cannot represent a private
|
|
540
577
|
* member in the emitted declaration file (TS4094).
|
|
541
|
-
*/
|
|
542
|
-
_invalidateAccountLists() {
|
|
543
|
-
this.clearCacheEntry('GET:/accounts');
|
|
544
|
-
this.clearCacheByPrefix('GET:/accounts?');
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Bust the cached member list and detail for an account after a membership
|
|
548
|
-
* mutation. The member list (`listAccountMembers`) and the detail
|
|
549
|
-
* (`getAccount`, which can embed the caller's membership) both go stale when
|
|
550
|
-
* the member set or a member's role changes.
|
|
551
578
|
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
579
|
+
* The forest keys themselves are NOT owned here — see
|
|
580
|
+
* `utils/accountCacheSweep`, which the user mixin has to reach as well.
|
|
554
581
|
*/
|
|
555
582
|
_invalidateAccountMembership(accountId) {
|
|
556
583
|
this.clearCacheEntry(`GET:/accounts/${encodeURIComponent(accountId)}/members`);
|
|
557
|
-
this.clearCacheEntry(
|
|
584
|
+
this.clearCacheEntry(oxyAccountDetailCacheKey(accountId));
|
|
585
|
+
// Inherited rows on descendant rosters are derived from this account's
|
|
586
|
+
// membership table — a targeted clear of only the mutated account's keys
|
|
587
|
+
// leaves every other cached `…/members` list serving stale inherited
|
|
588
|
+
// roles until MEDIUM TTL. Sweep all per-account sub-resource keys instead;
|
|
589
|
+
// the forest list keys (`GET:/accounts`, `GET:/accounts?…`) are excluded
|
|
590
|
+
// by the trailing slash on the prefix (see accountCacheSweep).
|
|
591
|
+
this.clearCacheByPrefix(OXY_ACCOUNT_PER_ACCOUNT_CACHE_PREFIX);
|
|
558
592
|
}
|
|
559
593
|
/**
|
|
560
594
|
* Bust every cached application list. `listAccountApps(accountId)` keys each
|
|
@@ -565,8 +599,8 @@ export function OxyServicesAccountsMixin(Base) {
|
|
|
565
599
|
* query-string list variants, never the `GET:/applications/<id>…`
|
|
566
600
|
* detail/sub-resource keys.
|
|
567
601
|
*
|
|
568
|
-
* Internal helper (leading underscore); see `
|
|
569
|
-
* this is public rather than `private`.
|
|
602
|
+
* Internal helper (leading underscore); see `_invalidateAccountMembership`
|
|
603
|
+
* for why this is public rather than `private`.
|
|
570
604
|
*/
|
|
571
605
|
_invalidateAppLists() {
|
|
572
606
|
this.clearCacheEntry('GET:/applications');
|