Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# AGENTS

Notes:
- Use React Native elements only; do not replace them with DOM elements in specs or dummy app code.
- Do not edit `build/` outputs manually; regenerate with the appropriate build command instead.
1 change: 1 addition & 0 deletions npm-api-maker/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ yarn-debug.log
yarn-error.log

# Expo
.expo/
.expo/*

# Project specific
Expand Down
33 changes: 24 additions & 9 deletions npm-api-maker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
"test": "expo-module test",
"prepare": "npm run clean && npm run build",
"prepublishOnly": "expo-module prepublishOnly && npm run build",
"expo-module": "expo-module",
"open:ios": "xed example/ios",
"open:android": "open -a \"Android Studio\" example/android",
"eslint": "eslint --ext .js,.jsx",
"eslint-find-rules": "eslint-find-rules",
"lint:eslint": "eslint --ext .js,.jsx",
"lint:eslint-find-rules": "eslint-find-rules",
"typecheck": "tsc --noEmit",
"watch": "tsc -w"
},
Expand Down Expand Up @@ -70,18 +69,20 @@
"@babel/eslint-parser": "^7.16.3",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.26.3",
"@jest/globals": "^30.2.0",
"babel-jest": "^30.0.0",
"@jest/globals": "~29.7.0",
"babel-jest": "~29.7.0",
"eslint": "^9.23.0",
"@types/react": "^18.3.12",
"@types/react": "~19.0.10",
"eslint-config-expo": "~9.2.0",
"eslint-find-rules": "^5.0.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react": "^7.23.2",
"expo": "~53.0.9",
"expo": "~53.0.25",
"expo-module-scripts": "^4.0.2",
"jest": "^30.0.0",
"typescript": "^5.9.3"
"jest": "~29.7.0",
"react": "19.0.0",
"react-native": "0.79.6",
"typescript": "~5.8.3"
},
"peerDependencies": {
"expo": "*",
Expand All @@ -92,5 +93,19 @@
"react-native": "*",
"react-native-vector-icons": "*"
},
"resolutions": {
"@expo/config-plugins": "~10.1.1",
"@expo/metro-config": "~0.20.18",
"metro": "^0.82.0",
"metro-config": "^0.82.0",
"metro-resolver": "^0.82.0"
},
"overrides": {
"@expo/config-plugins": "~10.1.1",
"@expo/metro-config": "~0.20.18",
"metro": "^0.82.0",
"metro-config": "^0.82.0",
"metro-resolver": "^0.82.0"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
2 changes: 1 addition & 1 deletion npm-api-maker/src/base-error.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

import {dig, digg} from "diggerize"
import errorMessages from "./error-messages"
import errorMessages from "./error-messages.js"

/**
* @typedef {object} BaseErrorArgsType
Expand Down
14 changes: 7 additions & 7 deletions npm-api-maker/src/base-model.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import Attribute from "./base-model/attribute"
import AttributeNotLoadedError from "./attribute-not-loaded-error"
import CacheKeyGenerator from "./cache-key-generator"
import Attribute from "./base-model/attribute.js"
import AttributeNotLoadedError from "./attribute-not-loaded-error.js"
import CacheKeyGenerator from "./cache-key-generator.js"
import Collection from "./collection.js"
import CommandsPool from "./commands-pool.js"
import Config from "./config.js"
import CustomError from "./custom-error.js"
import {digg} from "diggerize"
import FormDataObjectizer from "form-data-objectizer"
import * as inflection from "inflection"
import ModelName from "./model-name"
import NotLoadedError from "./not-loaded-error"
import ModelName from "./model-name.js"
import NotLoadedError from "./not-loaded-error.js"
import objectToFormData from "object-to-formdata"
import Reflection from "./base-model/reflection"
import Scope from "./base-model/scope"
import Reflection from "./base-model/reflection.js"
import Scope from "./base-model/scope.js"
import Services from "./services.js"
import ValidationError from "./validation-error.js"
import {ValidationErrors} from "./validation-errors.js"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/bootstrap/attribute-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import MoneyFormatter from "../money-formatter.js"
import PropTypes from "prop-types"
import {shapeComponent, ShapeComponent} from "set-state-compare/build/shape-component.js"
import strftime from "strftime"
import Text from "../utils/text"
import Text from "../utils/text.js"
import useI18n from "i18n-on-steroids/src/use-i18n.mjs"
import {View} from "react-native"

Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/bootstrap/attribute-rows.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AttributeRow from "./attribute-row"
import AttributeRow from "./attribute-row.js"
import BaseComponent from "../base-component.js"
import memo from "set-state-compare/build/memo.js"
import PropTypes from "prop-types"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/bootstrap/checkbox.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {shapeComponent, ShapeComponent} from "set-state-compare/build/shape-component.js"
import Checkbox from "../inputs/checkbox"
import Checkbox from "../inputs/checkbox.js"
import classNames from "classnames"
import {digs} from "diggerize"
import InvalidFeedback from "./invalid-feedback.js"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/bootstrap/checkboxes.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {shapeComponent, ShapeComponent} from "set-state-compare/build/shape-component.js"
import React, {useMemo} from "react"
import {digs} from "diggerize"
import {useForm} from "../form"
import {useForm} from "../form.js"
import * as inflection from "inflection"
import InvalidFeedback from "./invalid-feedback.js"
import memo from "set-state-compare/build/memo.js"
Expand Down
6 changes: 3 additions & 3 deletions npm-api-maker/src/bootstrap/input.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {digs} from "diggerize"
import inputWrapper from "../inputs/input-wrapper"
import {Input} from "../inputs/input"
import inputWrapper from "../inputs/input-wrapper.js"
import {Input} from "../inputs/input.js"
import InvalidFeedback from "./invalid-feedback.js"
import memo from "set-state-compare/build/memo.js"
import Money from "../inputs/money"
import Money from "../inputs/money.js"
import PropTypes from "prop-types"
import React from "react"
import {shapeComponent, ShapeComponent} from "set-state-compare/build/shape-component.js"
Expand Down
4 changes: 2 additions & 2 deletions npm-api-maker/src/bootstrap/paginate.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import BaseComponent from "../base-component.js"
import instanceOfClassName from "../instance-of-class-name.js"
import Link from "../link"
import Link from "../link.js"
import PropTypes from "prop-types"
import propTypesExact from "prop-types-exact"
import memo from "set-state-compare/build/memo.js"
import qs from "qs"
import React, {useMemo} from "react"
import Text from "../utils/text"
import Text from "../utils/text.js"
import Result from "../result.js"
import {shapeComponent} from "set-state-compare/build/shape-component.js"
import urlEncode from "../url-encode.js"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/bootstrap/radio-buttons.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {digs} from "diggerize"
import inputWrapper from "../inputs/input-wrapper"
import inputWrapper from "../inputs/input-wrapper.js"
import InvalidFeedback from "./invalid-feedback.js"
import PropTypes from "prop-types"
import propTypesExact from "prop-types-exact"
Expand Down
4 changes: 2 additions & 2 deletions npm-api-maker/src/bootstrap/select.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {digs} from "diggerize"
import inputWrapper from "../inputs/input-wrapper"
import {Select} from "../inputs/select"
import inputWrapper from "../inputs/input-wrapper.js"
import {Select} from "../inputs/select.js"
import InvalidFeedback from "./invalid-feedback.js"
import PropTypes from "prop-types"
import React from "react"
Expand Down
6 changes: 3 additions & 3 deletions npm-api-maker/src/bootstrap/sort-link.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import BaseComponent from "../base-component.js"
import * as inflection from "inflection"
import Icon from "../utils/icon"
import Icon from "../utils/icon.js"
import PropTypes from "prop-types"
import qs from "qs"
import memo from "set-state-compare/build/memo.js"
import React from "react"
import Text from "../utils/text"
import Text from "../utils/text.js"
import {shapeComponent} from "set-state-compare/build/shape-component.js"
import urlEncode from "../url-encode.js"
import useSorting from "../table/use-sorting.js"

import Link from "../link"
import Link from "../link.js"
import useQueryParams from "on-location-changed/build/use-query-params.js"

export default memo(shapeComponent(class ApiMakerBootstrapSortLink extends BaseComponent {
Expand Down
4 changes: 2 additions & 2 deletions npm-api-maker/src/cable-connection-pool.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import CableSubscriptionPool from "./cable-subscription-pool"
import CableSubscription from "./cable-subscription"
import CableSubscriptionPool from "./cable-subscription-pool.js"
import CableSubscription from "./cable-subscription.js"
import {dig} from "diggerize"
import RunLast from "./run-last.js"

Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/cable-subscription-pool.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import getChannelsConsumer from "./channels-consumer"
import getChannelsConsumer from "./channels-consumer.js"
import CommandsPool from "./commands-pool.js"
import Deserializer from "./deserializer.js"
import {digg} from "diggerize"
Expand Down
66 changes: 47 additions & 19 deletions npm-api-maker/src/can-can.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {digg} from "diggerize"
import {EventEmitter} from "eventemitter3"
/* eslint-disable sort-imports */
import * as inflection from "inflection"
import {EventEmitter} from "eventemitter3"
import {ReadersWriterLock} from "epic-locks"
import {digg} from "diggerize"
import Services from "./services.js"

const shared = {}
Expand All @@ -10,6 +11,8 @@ export default class ApiMakerCanCan {
abilities = []
abilitiesToLoad = []
abilitiesToLoadData = []
abilitiesGeneration = 0
loadingCount = 0
events = new EventEmitter()
lock = new ReadersWriterLock()

Expand All @@ -24,6 +27,8 @@ export default class ApiMakerCanCan {
const foundAbility = this.findAbility(abilityToUse, subject)

if (foundAbility === undefined) {
if (this.isReloading()) return false

let subjectLabel = subject

// Translate resource-models into class name strings
Expand Down Expand Up @@ -74,43 +79,53 @@ export default class ApiMakerCanCan {
return false
}

isReloading () {
return this.loadingCount > 0
}

async loadAbilities (abilities) {
await this.lock.read(async () => {
const promises = []
try {
await this.lock.read(async () => {
const promises = []

for (const abilityData of abilities) {
const subject = abilityData[0]
for (const abilityData of abilities) {
const subject = abilityData[0]

if (!subject) throw new Error(`Invalid subject given in abilities: ${subject} - ${JSON.stringify(abilities)}`)
if (!Array.isArray(abilityData[1])) throw new Error(`Expected an array of abilities but got: ${typeof abilityData[1]}: ${abilityData[1]}`)
if (!subject) throw new Error(`Invalid subject given in abilities: ${subject} - ${JSON.stringify(abilities)}`)
if (!Array.isArray(abilityData[1])) throw new Error(`Expected an array of abilities but got: ${typeof abilityData[1]}: ${abilityData[1]}`)

for (const ability of abilityData[1]) {
const promise = this.loadAbility(ability, subject)
for (const ability of abilityData[1]) {
const promise = this.loadAbility(ability, subject)

promises.push(promise)
promises.push(promise)
}
}
}

await Promise.all(promises)
})
await Promise.all(promises)
})
} finally {
if (this.loadingCount > 0) this.loadingCount -= 1
}
}

loadAbility (ability, subject) {
return new Promise((resolve) => {
ability = inflection.underscore(ability)
const normalizedAbility = inflection.underscore(ability)

if (this.isAbilityLoaded(ability, subject)) {
if (this.isAbilityLoaded(normalizedAbility, subject)) {
resolve()
return
}

const foundAbility = this.abilitiesToLoad.find((abilityToLoad) => digg(abilityToLoad, "ability") == ability && digg(abilityToLoad, "subject") == subject)
const foundAbility = this.abilitiesToLoad.find((abilityToLoad) => digg(abilityToLoad, "ability") == normalizedAbility &&
digg(abilityToLoad, "subject") == subject
)

if (foundAbility) {
foundAbility.callbacks.push(resolve)
} else {
this.abilitiesToLoad.push({ability, callbacks: [resolve], subject})
this.abilitiesToLoadData.push({ability, subject})
this.abilitiesToLoad.push({ability: normalizedAbility, callbacks: [resolve], subject})
this.abilitiesToLoadData.push({ability: normalizedAbility, subject})

this.queueAbilitiesRequest()
}
Expand All @@ -128,11 +143,14 @@ export default class ApiMakerCanCan {
async resetAbilities () {
await this.lock.write(() => {
this.abilities = []
this.abilitiesGeneration += 1
this.loadingCount += 1
})
this.events.emit("onResetAbilities")
}

sendAbilitiesRequest = async () => {
const generation = this.abilitiesGeneration
const abilitiesToLoad = this.abilitiesToLoad
const abilitiesToLoadData = this.abilitiesToLoadData

Expand All @@ -145,6 +163,16 @@ export default class ApiMakerCanCan {
})
const abilities = digg(result, "abilities")

if (generation !== this.abilitiesGeneration) {
for (const abilityData of abilitiesToLoad) {
for (const callback of abilityData.callbacks) {
callback()
}
}

return
}

// Set the loaded abilities
this.abilities = this.abilities.concat(abilities)

Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/draggable-sort/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, {useMemo} from "react"
import {Animated, PanResponder} from "react-native"
import {shapeComponent, ShapeComponent} from "set-state-compare/build/shape-component.js"
import Controller from "./controller.js"
import DraggableSortItem from "./item"
import DraggableSortItem from "./item.js"
import {EventEmitter} from "eventemitter3"
import memo from "set-state-compare/build/memo.js"
import PropTypes from "prop-types"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/inputs/attachment.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BaseComponent from "../base-component.js"
import classNames from "classnames"
import {Input as ApiMakerInput} from "@kaspernj/api-maker/build/inputs/input"
import Checkbox from "./checkbox"
import Checkbox from "./checkbox.js"
import memo from "set-state-compare/build/memo.js"
import PropTypes from "prop-types"
import React from "react"
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/inputs/checkbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PropTypes from "prop-types"
import memo from "set-state-compare/build/memo.js"
import {shapeComponent} from "set-state-compare/build/shape-component.js"
import useInput from "../use-input.js"
import {useForm} from "../form"
import {useForm} from "../form.js"
import useUpdatedEvent from "../use-updated-event.js"

export default memo(shapeComponent(class ApiMakerInputsCheckbox extends BaseComponent {
Expand Down
2 changes: 1 addition & 1 deletion npm-api-maker/src/inputs/checkboxes.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import BaseComponent from "../base-component.js"
import classNames from "classnames"
import {digs} from "diggerize"
import inputWrapper from "./input-wrapper"
import inputWrapper from "./input-wrapper.js"
import * as inflection from "inflection"
import InvalidFeedback from "../bootstrap/invalid-feedback.js"
import PropTypes from "prop-types"
Expand Down
Loading