teamplay 0.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.
@@ -0,0 +1,63 @@
1
+ import { useMemo, forwardRef as _forwardRef, memo, createElement as el, Suspense } from 'react'
2
+ import { createCaches } from '@startupjs/cache'
3
+ import { __increment, __decrement } from '@startupjs/debug'
4
+ import { pipeComponentMeta, pipeComponentDisplayName, ComponentMetaContext, useUnmount } from './helpers.js'
5
+
6
+ export default function wrapIntoSuspense (
7
+ ObservedComponent
8
+ ) {
9
+ const { forwardRef, suspenseProps } = ObservedComponent.__observerOptions
10
+ if (!(suspenseProps && suspenseProps.fallback)) {
11
+ throw Error(
12
+ '[observer()] You must pass at least ' +
13
+ 'a fallback parameter to suspenseProps'
14
+ )
15
+ }
16
+
17
+ let SuspenseWrapper = (props, ref) => {
18
+ const cache = useMemo(() => {
19
+ __increment('ObserverWrapper.cache')
20
+ return createCaches(['styles', 'model'])
21
+ }, [])
22
+ // TODO: using useState instead of useMemo will keep this intact during Fast Refresh
23
+ // Research if we can change it to use it.
24
+ // const [componentMeta] = React.useState({
25
+ // componentId: $root.id(),
26
+ // createdAt: Date.now(),
27
+ // cache
28
+ // })
29
+ const componentMeta = useMemo(function () {
30
+ return {
31
+ // componentId: $root.id(), // TODO: implement creating a unique component guid here (if it's needed anymore)
32
+ createdAt: Date.now(),
33
+ cache
34
+ }
35
+ }, [])
36
+
37
+ useUnmount(() => {
38
+ __decrement('ObserverWrapper.cache')
39
+ cache.clear()
40
+ })
41
+
42
+ if (forwardRef) props = { ...props, ref }
43
+
44
+ return (
45
+ el(ComponentMetaContext.Provider, { value: componentMeta },
46
+ el(Suspense, suspenseProps,
47
+ el(ObservedComponent, props)
48
+ )
49
+ )
50
+ )
51
+ }
52
+
53
+ // pipe only displayName because forwardRef render function
54
+ // do not support propTypes or defaultProps
55
+ pipeComponentDisplayName(ObservedComponent, SuspenseWrapper, 'StartupjsObserverWrapper')
56
+
57
+ if (forwardRef) SuspenseWrapper = _forwardRef(SuspenseWrapper)
58
+ SuspenseWrapper = memo(SuspenseWrapper)
59
+
60
+ pipeComponentMeta(ObservedComponent, SuspenseWrapper)
61
+
62
+ return SuspenseWrapper
63
+ }
@@ -0,0 +1 @@
1
+ export default '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
@@ -0,0 +1,51 @@
1
+ import GUID_PATTERN from './GUID_PATTERN.js'
2
+
3
+ export function belongsTo (collectionName) {
4
+ return {
5
+ type: 'string',
6
+ pattern: GUID_PATTERN,
7
+ $association: {
8
+ type: 'belongsTo',
9
+ collection: collectionName
10
+ }
11
+ }
12
+ }
13
+
14
+ export function hasMany (collectionName) {
15
+ return {
16
+ type: 'array',
17
+ items: {
18
+ type: 'string',
19
+ pattern: GUID_PATTERN
20
+ },
21
+ $association: {
22
+ type: 'hasMany',
23
+ collection: collectionName
24
+ }
25
+ }
26
+ }
27
+
28
+ export function hasManyFlags (collectionName) {
29
+ return {
30
+ type: 'object',
31
+ patternProperties: {
32
+ [GUID_PATTERN]: { type: 'boolean' }
33
+ },
34
+ additionalProperties: false,
35
+ $association: {
36
+ type: 'hasManyFlags',
37
+ collection: collectionName
38
+ }
39
+ }
40
+ }
41
+
42
+ export function hasOne (collectionName) {
43
+ return {
44
+ type: 'string',
45
+ pattern: GUID_PATTERN,
46
+ $association: {
47
+ type: 'hasOne',
48
+ collection: collectionName
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Pick properties from json-schema to be used in a form.
3
+ * Supports simplified schema (just the properties object) and full schema.
4
+ * Performs extra transformations like auto-generating `label`.
5
+ * `createdAt`, `updatedAt`, `id`, `_id` fields are excluded by default.
6
+ * @param {Object} schema
7
+ * @param {Object|Array} options - exclude or include fields. If array, it's the same as passing { include: [...] }
8
+ * @param {Array} options.include - list of fields to pick (default: all)
9
+ * @param {Array} options.exclude - list of fields to exclude (default: none)
10
+ * @param {Boolean} options.freeze - whether to deep freeze the result (default: true)
11
+ */
12
+ export default function pickFormFields (schema, options) {
13
+ try {
14
+ let include, exclude, freeze
15
+ if (Array.isArray(options)) {
16
+ include = options
17
+ } else {
18
+ ;({ include, exclude, freeze = true } = options || {})
19
+ }
20
+ exclude ??= []
21
+ if (!schema) throw Error('pickFormFields: schema is required')
22
+ schema = JSON.parse(JSON.stringify(schema))
23
+ if (schema.type === 'object') {
24
+ schema = schema.properties
25
+ }
26
+ for (const key in schema) {
27
+ if (shouldIncludeField(key, schema[key], { include, exclude })) {
28
+ const field = schema[key]
29
+ if (!field.label) field.label = camelCaseToLabel(key)
30
+ } else {
31
+ delete schema[key]
32
+ }
33
+ }
34
+ if (freeze) return new Proxy(schema, deepFreezeHandler)
35
+ return schema
36
+ } catch (err) {
37
+ throw Error(`
38
+ pickFormFields: ${err.message}
39
+ schema:\n${JSON.stringify(schema, null, 2)}
40
+ `)
41
+ }
42
+ }
43
+
44
+ // Proxy handlers to deep freeze schema to prevent accidental mutations.
45
+ // For this, when we .get() a property, we also return the same recursive Proxy handler if it's an object.
46
+ const deepFreezeHandler = {
47
+ get (target, prop) {
48
+ const value = target[prop]
49
+ if (typeof value === 'object' && value !== null) {
50
+ return new Proxy(value, deepFreezeHandler)
51
+ }
52
+ return value
53
+ },
54
+ set () {
55
+ throw Error(ERRORS.schemaIsFrozen)
56
+ }
57
+ }
58
+
59
+ function shouldIncludeField (key, field, { include, exclude = [] } = {}) {
60
+ if (!field) throw Error(`field "${key}" does not have a schema definition`)
61
+ if (include?.includes(key)) return true
62
+ if (exclude.includes(key)) return false
63
+ // if field has 'input' specified then it's an explicit indicator that it can be used in forms,
64
+ // so the default exclusion rules don't apply
65
+ if (!field.input) {
66
+ // exclude some meta fields by default
67
+ if (DEFAULT_EXCLUDE_FORM_FIELDS.includes(key)) return false
68
+ // exclude foreign keys by default
69
+ // Foreign keys have a custom `$association` property set by belongsTo/hasMany/hasOne helpers
70
+ if (field.$association) return false
71
+ }
72
+ // if include array is not explicitly set, include all fields by default
73
+ if (!include) return true
74
+ return false
75
+ }
76
+
77
+ const DEFAULT_EXCLUDE_FORM_FIELDS = ['id', '_id', 'createdAt', 'updatedAt']
78
+
79
+ // split into words, capitalize first word, make others lowercase
80
+ function camelCaseToLabel (str) {
81
+ return str
82
+ .replace(/([A-Z])/g, ' $1')
83
+ .toLowerCase()
84
+ .replace(/^./, (s) => s.toUpperCase())
85
+ }
86
+
87
+ const ERRORS = {
88
+ schemaIsFrozen: `
89
+ Form fields are immutable.
90
+ If you want to change them, clone them with \`JSON.parse(JSON.stringify(FORM_FIELDS))\`.
91
+
92
+ If you want to do it inside react component, you can use this pattern for the most effective cloning:
93
+
94
+ \`\`\`
95
+ const $fields = useValue$(useMemo(() => JSON.parse(JSON.stringify(FORM_FIELDS)), []))
96
+ \`\`\`
97
+
98
+ and then pass $fields to the Form component like this:
99
+
100
+ \`\`\`
101
+ <Form $fields={$fields} $value={$value} />
102
+ \`\`\`
103
+ `
104
+ }
package/server.js ADDED
@@ -0,0 +1,12 @@
1
+ import createChannel from '@startupjs/channel/server'
2
+ import { connection, setConnection, setFetchOnly } from './orm/connection.js'
3
+
4
+ export { default as ShareDB } from 'sharedb'
5
+
6
+ export default function initConnection (backend, options) {
7
+ if (!backend) throw Error('backend is required')
8
+ if (connection) throw Error('Connection already exists')
9
+ setConnection(backend.connect())
10
+ setFetchOnly(true)
11
+ return createChannel(backend, options)
12
+ }
package/utils/uuid.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const { v4: uuid } = require('uuid')
2
+ module.exports = uuid